Your browser lacks required capabilities. Please upgrade it or switch to another to continue.
Loading…
<b>IMPORTANT NOTE: Another RPG Engine uses ES6 components and may not function on all browswers. Google Chrome and Firefox should work, but other browsers may not. If you encounter errors, make sure you are using the most current version of your browser.</b>
Another RPG Engine contains a lot of components. To use them all together, you will need a special compiler.
I use <a href="http://www.motoslave.net/tweego/" target="_blank">Tweego</a>, which has an installer <a href="https://github.com/ChapelR/tweego-installer" target="_blank">here</a>. <b>Note the instructions regarding <code>StoryData</code> if you do not wish to use Twine to create your project.</b>
Once you have everything set, you can open your comand prompt (search "cmd" in Windows) and use the "tweego" command to compile Twine stories. If you place your story file in a folder with JavaScript, CSS, and twee files, and target that folder with the "tweego" command, they will all be bundled together. The default download package has already lumped these into a "src" folder for you.
The "src" folder is grouped into the following subfolders:
* {{{javascript}}}, for JavaScript files
** {{{core}}}, for core files necessary for the engine's function
** {{{custom}}}, for user-defined elements such as character definitions
* {{{macros-additional}}}, third-party SugarCube macros not included in the default SugarCube package
* {{{passages-core}}}, for Twee files necessary for the engine's function
* {{{passages-custom}}}, for optional mods and your own Twee files
* {{{stylesheets}}}, for CSS stylesheets
In the final product, everything will become part of the same story. However, there are a few rules that you must keep in mind:
<b>If there is a conflict in the data between two story files</b>, such as passages with the same names, <b>the last file compiled will overwrite the first.</b> Tweego will compile files in the same order they are seen in the directory, which generally means in alphabetical order. Fortunately, "passages-custom" comes after "passages-core", so any unique passages you make will overwrite the defaults.
However, <b>be careful if you have any passages with the same names</b>. If anything in the engine gets overwritten, the whole thing could break. This probably won't be a problem for most passages, but <b>be aware that the engine uses the special passages StoryInit, PassageReady, and PassageDone.</b> You will likely want to use these passages for your own purposes in your story, so make sure there is no conflict in the core files.
Another resource you may find helpful is <a href="https://www.johnayliff.com/works.html" target="_blank">a SugarCube language definition for Notepad++</a>, by John Ayliff. SugarCube does not have a default language definition in Twine, so this may be helpful for organizing your code. Click on "save link as", save the file anywhere on your computer, and import the file under Language -> User Defined Language in Notepad++. If you use a different coding environment, there is also <a href="https://github.com/otommod/twee-sugarcube.vim" target="_blank">a .vim version</a> by otommod.
The engine uses ChapelR's custom SugarCube macros, the documentation for which can be found <a href="https://twinelab.net/custom-macros-for-sugarcube-2/#/" target="_blank">here</a>.
<b>Summary:</b>
* Download Tweego <a href="http://www.motoslave.net/tweego/" target="_blank">here</a>. If you can't follow the directions, use the installer <a href="https://github.com/ChapelR/tweego-installer" target="_blank">here</a>.
* You can compile multiple story and twee files at once, but later conflicts will overwrite earlier ones.
* Store customized engine passages in passages-custom to avoid conflicts.This passage will discuss non-essential RPG features with only skeletal implementations in the default engine, but which may be of interest. This will contain rather advanced discussion of code and will build on the principles discussed in the design passage, so it is recommended you read [[Documentation|Documentation (Advanced)]] and [[Design]] first.
<h1>Table of Contents</h1>
><a href="#flow">Battle Flow and Turn Order</a>
>><a href="#flow.1">Method 1: Ranked Order</a>
>>><a href="#flow.1.a">Adding Variance</a>
>><a href="#flow.2">Method 2: Action Time</a>
>>><a href="#flow.2.a">Action Time with Threshold</a>
>><a href="#flow.3">Method 3: Timeline</a>
>>><a href="#flow.3.a">Duel (One-Character) Systems</a>
>><a href="#flow.4">Transparency</a>
><a href="#progress">Progression and Leveling Up</a>
>><a href="#progress.levels">Levels</a>
>>><a href="#progress.levels.1">Calculating XP Requirements</a>
>>><a href="#progress.levels.2">What Do Levels Do?</a>
>>><a href="#progress.levels.code">Implementation</a>
>><a href="#progress.pointbuy">Point-Buy</a>
>>><a href="#progress.pointbuy.code">Implementation</a>
>><a href="#progress.equip">Equipment-Based Progression</a>
><a href="#battlegrid">Battle Grid</a>
>><a href="#battlegrid.1">Setup</a>
>><a href="#battlegrid.2">Features</a>
><a href="#missMechanics">Misses and Critical Hits</a>
>><a href="#misses">Accuracy and Misses</a>
>><a href="#crits">Critical Hits</a>
><a href="#bestiaryDocs">Bestiary/Enemy Encyclopedia</a>
>><a href="#bestiaryClass">Bestiary Class</a>
>><a href="#bestiaryEntryClass">BestiaryEntry Class</a>
><a href="#crisis">Crisis/Limit Breaks</a>
>><a href="#crisis.code">Implementation</a>
>><a href="#crisis.stash">Stash</a>
<h2 id="flow">Battle Flow and Turn Order</h2>
In <i>Cartoon Battle</i>, everyone can take their turns in any order, and each party's turn happens all at once. This system is very simple to implement and balance: everyone gets the same number of actions and acts at the same rate. I recommend it to novice developers for this reason.
This is not the only way to structure turn order, however. Many RPGs include a "speed" or "agility" stat that makes some characters act before others.
<h3 id="flow.1">Method 1: Ranked Order</h3>
The simplest way to implement this is to have everyone act in order of decreasing Speed. So, for instance, if we have three characters with 10, 5, and 4 Speed and an enemy with 9 Speed, every round will go in the order of Character A - Enemy - Character B - Character C. If we boost the enemy to 11 speed, they'd now go before any player characters, which could make them quite tricky! Under this model, everyone would still get the same number of turns -- after each round, we'd just repeat the queue. This means balancing actions is still fairly simple. However, we do gain added complexity by fixing characters' positions in the turn order -- especially if there are actions that can assist or set up other characters' abilities, such as buff and debuff effects. Players will need to construct their strategy around the turn order instead of being able to change when it suits them. Assuming a gradual increase in stats over the course of the game, players are also encouraged to increase their Speed to act before enemies and gain the advantage.
Below is an example of how to implement this system in SugarCube:
First, we will need to place every battling character into one array. In the default system, enemies and player characters are completely separate. One method that would work would be to create an <i>array of arrays</i> that links both enemy and player arrays into one object. That would look like this:
{{{
<<set $actors = [$puppets,$enemies]>>
}}}
If you run a {{{for}}} loop over {{{$actors}}}, you can iterate over both enemy and player parties at once -- as well as potential other parties, if you want to design complex multi-front battles.
Then you'd want to use the code in the "Initiative Model: Ranked Order" passage, which is copied here:
{{{
<<set _initiative = -1>>
/* The -1 is necessary if you want a Speed of 0 to be possible. */
<<for _i, _party range $actors>>
<<for _j, _actor range _party>>
<<if not _actor.dead and not _actor.stunned>>
/* If you have more paralysis effects, you'll need to add each here. Consider adding a convenient "can't act" attribute flipped by all of them if this becomes prohibitive.
<<if not _actor.isDone and _actor.speed > _initiative>>
/* (not _actor.isDone) prevents us from selecting characters who have already acted this round. They'll be skipped over, and the next-fastest character will get the initiative. */
<<set _subj = [_i,_j]>>
<<set _initiative = _actor.speed>>
/* This sets the current character's Speed as the new bar to clear. If no one's faster, no one else will pass the if check above and this character will remain the current subject. If someone else is faster, they'll become the subject and the _initiative variable will be updated to match their Speed. This ensures that subject status will be granted to the fastest character. */
<<elseif not _actor.isDone and _actor.speed == _initiative>>
/* You'll need a handler for this case, or else the character with the higher index order will get the initiative in the case of a match. This handler can be anything, including nothing at all. If you want to be nice, you could automatically give the player the initiative in the case of a tie. Here I've provided the fairest possible option: a coin flip. */
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = [_i,_j]>>
<</if>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<if _subj[0] == $actors.indexOf($puppets)>>
<<set $B.subject = [$puppets[_j],"$puppets["+_j+"]"]>>
<<elseif _subj[0] == $actors.indexOf($enemies)>>
<<set $B.subject = [$enemies[_j],"$enemies["+_j+"]"]>>
<<set $B.turn = "enemy">>
<<else>>
(You should write an error message here.)
<</if>>
<</if>>
}}}
(This is far more complicated than it needs to be because Twine does not preserve object references. In a proper coding engine we could just {{{
<<set $subject = _actor>>
}}} within the loop and be done with it, but Twine necessitates the second section.)
Note that this assumes a character's Speed stat will never go below 0.
You can make this a widget, or another passage that is run after every character's turn. Presumably, you would also modify the "Battle!" passage to only display commands for the active subject, with a handler in case the enemy has the active turn (probably forwarding the player to another passage for the enemy turn).
<h4 id="flow.1.a">Adding Variance</h4>
Unfortunately, the above model has a weakness: Speed has a binary, rather than variable, effect. With stats such as Attack and Defense, every point matters, because it contributes to a numerical value. But once you have more Speed than the enemy, there's no benefit to gaining more Speed. This can lead to Speed losing value compared to other stats, and making Speed-focused characters weaker overall.
Some games patch this problem by making the rules less rigid: instead of being determined by the absolute Speed values every time, initiative is instead determined through a random formula influenced by the Speed stat. Some games, such as <i>Dungeons & Dragons</i>, can use quite variable formulas, but for this example I will model a simpler one, varying characters' Speed stats by a certain percentage of their base values.
In SugarCube, you could implement this feature like so:
{{{
<<for _party range $actors>>
<<for _actor range _party>>
<<if not _actor.dead and not _actor.stunned>>
<<set _actor.initiative = _actor.speed>>
<<set _variance = random(-$VARIANCE_BOUND,$VARIANCE_BOUND)>>
<<set _variance /= 100>>
/* Twine's random() function requires integer bounds, but we (presumably) plan to vary characters' Speed stats by a small proportion. We can do this by setting $VARIANCE_BOUND to an integer percentage value (probably in StoryInit) and dividing by 100 to get the decimal value. */
<<set _actor.initiative += _actor.speed * _variance>>
<</if>>
<</for>>
<</for>>
}}}
You can then use the same code as in the previous section, except you would compare {{{_initative}}} to {{{_actor.initiative}}} instead of {{{_actor.speed}}}.
As an example, let us say we set {{{$VARIANCE_BOUND}}} to 10. This would give {{{_variance}}} a random value from anywhere between -10 and 10. We then convert it into a decimal, making it a random value between -.1 and .1. We then take {{{_actor.initiative}}}, which is initialized at the same value as {{{_actor.speed}}}, and add a fraction of {{{_actor.speed}}} to get the character's final initiative value. The size and sign of the fraction is determined by {{{_variance}}}.
If the character had 100 Speed, their initiative value could be anywhere between 90 and 110. This means a character with 90 Speed could beat them in the turn order, if they rolled high and the 100 Speed character rolled low. However, the 100 Speed character would still get the first turn more often than not. Additionally, a character with 81 Speed would never beat them in turn order even if they got the maximum bonus and the 100 Speed character got the maximum penalty: {{{81 + (81*0.1) = 89.1}}} is less than {{{100 + (100*-0.1) = 90}}}.
You can add more randomness by increasing the variance bound.
This variance makes a Speed focus a more optimal gameplay decision: even if you're fast, more Speed increases the chance you'll get the initiative, rather than ceasing to matter once you get 1 point more than your opponent.
I, personally, hate this method with a burning passion, because I feel turn order is too important of a thing to leave up to chance, and makes it nigh-impossible for the player to implement long-term strategy if they cannot predict the turn order.
Some games feel this still leaves Speed a suboptimal stat, and have it influence other aspects of battle as well, such as accuracy, evasion, and critical hit rates. (These features are not in the default engine because I despise them, but I may include a tutorial on implementing them if there is demand.)
<h3 id="flow.2">Method 2: Action Time</h3>
If you've played any <i>Final Fantasy</i> game after <i>Final Fantasy III</i>, or any game made in RPG Maker 2003, you will be familiar with this system. (Yes, even <i>Final Fantasy X</i>.)
The most familiar form, seen in the examples I mentioned, is an odd method of merging turn-based RPGs with reaction-based action games: characters have gauges that fill up in real time, and characters can take an action when it's full. Usually this process pauses while you choose your action, but sometimes you have to choose quick while enemies are still getting turns.
Such a real-time, image-based system is obviously not suited for Twine. You could theoretically do it with {{{<<timed>>}}} and {{{<<repeat>>}}} macros, maybe...? But your time would almost certainly be better spent in another environment entirely.
What I've described is just an abstraction, though. If we look at the variables underneath, we can create a similar system even in our static Twine.
First, we're going to increment a separate "initiative" attribute based on some formula. The formula can include anything, including random variance, but for the purposes of this example let's just make the increment equal to the Speed stat:
{{{
<<for _party range $actors>>
<<for _actor range _party>>
<<if not _actor.dead>>
<<set _actor.initiative += _actor.speed>>
<</if>>
<</for>>
<</for>>
}}}
We then use a modified form of our code from <a class="noExternal" href="#flow.1">Method 1</a>:
{{{
<<set _initiative = -1>>
<<for _i, _party range $actors>>
<<for _j, _actor range _party>>
<<if not _actor.dead>>
<<if _actor.initiative > _initiative>>
<<set _subj = [_i,_j]>>
<<set _initiative = _actor.initiative>>
<<elseif _actor.initiative == _initiative>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = [_i,_j]>>
<</if>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<if _subj[0] == $actors.indexOf($puppets)>>
<<set $B.subject = [$puppets[_j],"$puppets["+_j+"]"]>>
<<elseif _subj[0] == $actors.indexOf($enemies)>>
<<set $B.subject = [$enemies[_j],"$enemies["+_j+"]"]>>
<<set $B.turn = "enemy">>
<<else>>
(You should write an error message here.)
<</if>>
<<set $B.subject[0].initiative = 0>>
<</if>>
}}}
This assumes every character's "initiative" attribute is set to 0 at the start of battle.
To keep things simple, the current subject's initiative is set to 0 when they take their turn. In this code, this automatically happens as soon as they're selected; you may want to make this happen in the action phase instead, if you want some actions to drain initiative by different amounts. Maybe there's a "wait" option that only halves it, for instance?
The main difference here is that we do not ignore characters who have acted in the round already: if someone gains more initiative, they can get more turns than another character. This effectively eliminates the concept of "rounds" entirely; characters will just keep taking turns, in no fixed order.
This system will be functionally similar to Method 1 when there is little difference between characters' Speed stats. For example, let's look at a battle where Character A has 11 Speed, Enemy B has 10 Speed, and Character C has 9 Speed:
Initiative at turn 0:
A = 11
B = 10
C = 9
A gets the turn. Their initiative goes to 0.
Initiative at turn 1:
A (1 turn) = 11
B (0 turns) = 20
C (0 turns) = 18
B gets the turn. Their initiative goes to 0.
Initiative at turn 2:
A (1 turn) = 22
B (1 turn) = 10
C (0 turns) = 27
C gets the turn. Their initiative goes to 0.
Initiative at turn 3:
A (1 turn) = 33
B (1 turn) = 20
C (1 turn) = 9
A gets the turn.
Initiative at turn 4:
A (2 turns) = 11
B (1 turn) = 30
C (1 turn) = 18
B gets the turn.
Initiative at turn 5:
A (2 turns) = 22
B (2 turns) = 10
C (1 turn) = 27
C gets the turn. Notice that the initiative values are identical to those on turn 2: we've completed a loop. The characters will continue taking turns in this pattern, which is identical to the pattern in Method 1: characters will act in order of descending Speed, and then repeat.
But that was when the Speed values only differed by 1. Let's look at what happens with bigger differences: A now has 30, B now has 20, and C now has 10.
Initiative at turn 0:
A = 30
B = 20
C = 10
A gets the turn.
Initiative at turn 1:
A (1 turn) = 30
B (0 turns) = 40
C (0 turns) = 20
B gets the turn.
Initiative at turn 2:
A (1 turn) = 60
B (1 turn) = 20
C (0 turns) = 30
A gets the turn. But wait! C hasn't gotten a single turn, but A has now moved twice! That's the key difference in an action time system: faster characters will get more turns total, instead of everyone getting the same number of actions. This is much, <i>much</i> harder to balance. If A gets twice as many turns as B, they've effectively doubled all their stats by only doubling one: even if all their other stats are the same as B's, they're attacking twice as often, defending twice as often, using utility skills twice as often. If Method 1 undervalued Speed, action time <i>overvalues</i> it -- Speed can come to dominate all other stats, because more turns are almost always more desirable than fewer but stronger turns. This can be mitigated in a subtractive defense system where more attacks don't necessarily correlate with more results, but is exacerbated by skills that don't depend on other stats, such as most status effects.
You can still balance this through careful tweaking of the formulas -- maybe initiative gain is only half Speed, for instance -- but it's not a task I recommend for novice developers. It's much easier to include this sort of "multi-turn" functionality through specific actions you can control directly. For example, in <i>Cartoon Battle</i> Rogue's basic attack hits twice, but is weaker than the other basic attacks.
<h4 id="flow.2.a">Action Time with Threshold</h4>
If you're interested in more accurately modeling the version seen in <i>Final Fantasy</i>, you can choose turns based on whose initiative crosses a "finish line" first. <i>Bonfire</i> uses this method, and its algorithm is explained <a href="http://bonfire-game.wikia.com/wiki/Turn_Order" target="_blank">here</a>. Implementation in SugarCube would look something like this:
{{{
<<set _threshold = $ACTION_THRESHOLD>> /* You would set $ACTION_THRESHOLD to some constant in StoryInit. */
<<set _pastThreshold = false>>
/* check for characters already at threshold */
<<for _party range $actors>>
<<if _pastThreshold>>
<<break>>
<</if>>
<<for _actor range _party>>
<<if not _actor.dead>>
<<if _actor.initiative >= _threshold>>
<<set _pastThreshold = true>>
<<break>>
<</if>>
<</if>>
<</for>>
<</for>>
/* initiative gain */
<<for not _pastThreshold>>
<<for _party range $actors>>
<<for _actor range _party>>
<<if not _actor.dead>>
<<set _actor.initiative += 20 + 0.2 * _actor.speed>>
/* This is the Bonfire formula, but you can add your own */
<<if _actor.initiative >= _threshold>>
<<set _pastThreshold = true>>
<</if>>
<</if>>
<</for>>
<</for>>
<</for>>
/* initiative comparison */
<<for _i, _party range $actors>>
<<for _j, _actor range _party>>
<<if not _actor.dead>>
<<if _actor.initiative > _threshold>>
<<set _subj = [_i,_j]>>
<<set _threshold = _actor.initiative>>
<<elseif _actor.initiative == _threshold>>
<<if def _subj>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = [_i,_j]>>
<</if>>
<<else>>
<<set _subj = [_i,_j]>>
<</if>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<if _subj[0] == $actors.indexOf($puppets)>>
<<set $B.subject = [$puppets[_j],"$puppets["+_j+"]"]>>
<<elseif _subj[0] == $actors.indexOf($enemies)>>
<<set $B.subject = [$enemies[_j],"$enemies["+_j+"]"]>>
<<set $B.turn = "enemy">>
<<else>>
(You should write an error message here.)
<</if>>
<<set $B.subject[0].initiative -= $ACTION_THRESHOLD>>
<</if>>
}}}
To show an example of what this would look like, we'll use the fastest enemy in <i>Bonfire</i>, the Bladewolf, which has 70 Speed; the fastest hero in <i>Bonfire</i>, Assassin, who starts with 40 Speed; and the slowest hero in <i>Bonfire</i>, Knight, who starts with 15 Speed.
Turn 1:
B: 34 - 68 - <b>102</b>
A: 28 - 56 - 84
K: 23 - 46 - 69
Bladewolf gets the turn, and loses 100 initiative. Bladewolf has taken <b>1 turn.</b>
Turn 2:
B: 02 - 36
A: 84 - <b>112</b>
K: 69 - 92
Assassin gets the turn. Turn order so far is <b>B, A</b>.
Turn 3:
B: 36 - 70
A: 12 - 40
K: 92 - <b>115</b>
Knight gets the turn. Turn order so far is <b>B, A, K</b>.
Turn 4:
B: 70 - <b>104</b>
A: 40 - 68
K: 15 - 38
Turn order so far is <b>B, A, K, B</b>.
Turn 5:
B: 04 - 38 - 72
A: 68 - 96 - <b>124</b>
K: 38 - 61 - 84
Turn order so far is <b>B, A, K, B, A</b>.
Turn 6:
B: 72 - 106
A: 24 - 52
K: 84 - <b>107</b>
Knight will get the turn, then Bladewolf. Turn order so far is <b>B, A, K, B, A, K, B</b>.
Turn 8:
B: 06 - 40 - 74 - 108
A: 52 - 80 - 98 - <b>126</b>
K: 07 - 30 - 53 - 76
Assassin will get the turn, then Bladewolf. Turn order so far is <b>B, A, K, B, A, K, B, A, B</b>. Bladewolf lapped Knight after 9 turns.
Compared to straight highest-wins initiative:
Turn 1:
B: <b>34</b>
A: 28
K: 23
Turn order: <b>B</b>.
Turn 2:
B: 34
A: <b>56</b>
K: 46
Turn order: <b>B, A</b>.
Turn 3:
B: 68
A: 28
K: <b>69</b>
Turn order: <b>B, A, K</b>.
Turn 4:
B: <b>102</b>
A: 56
K: 23
Turn order: <b>B, A, K, B</b>.
Turn 5:
B: 34
A: <b>84</b>
K: 46
Turn order: <b>B, A, K, B, A</b>.
Turn 6:
B: 68
A: 28
K: <b>69</b>
Turn order: <b>B, A, K, B, A, K</b>. Notice that this distribution is identical to that of turn 3, which means we have entered a stable loop. Bladewolf will never lap Knight in this scenario.
The key difference here is that the simple method always resets initiative to 0 when the subject takes a turn no matter how much they accumulated, but the threshold method allows initiative to carry over if the threshold is overshot. This gives greater weight to differences in Speed stats and makes stable loops less likely. However, designing the correct threshold is itself a challenge; if we were to use a straight gain = Speed formula, anyone with 100 Speed would be able to take turns continuously! The threshold method is best suited to slow gain formulas with less dependence on stats, for this reason. You could also make the threshold flexible, such as tying it to three times the fastest or slowest character's gain rate, but this runs an even greater risk of making stat differences get out of hand.
<h3 id="flow.3">Method 3: Timeline</h3>
Characters' turns are placed at certain points in a timeline. When time advances to their point, they take their turn, and are then placed at another point.
This method is similar to action time, but works in reverse in terms of algorithm: we have everyone's initiative values count <i>down</i>, and characters take their turns when it hits 0.
We'd first need some way of creating an initial turn order. For now, let's assume whoever has the highest Speed stat goes first, and everyone lower than them goes 1 "tick" later for every point of Speed difference. So with characters of Speed 10, 8, 5, and 4, our starting timeline would look like this:
{{{
[A]-[B]--[C][D]
}}}
where dashes are empty ticks.
From here, A would take an action and be placed at a later point in the timeline. This determination could be based on Speed, but it's more commonly based on action; some actions will require the characters to wait longer before they can act again. For instance, attacking with a heavy greatsword might push a character back 6 ticks, while attacking with a knife only pushes them back 4. Maybe casting a spell only pushes them back 3 ticks, but requires 2 additional ticks of wind-up time while they chant the incantation, during which time they may be vulnerable.
After their action, A would be placed ahead in the timeline, and the timeline would advance; everyone's initiative would go down by 1. There's no one in the next tick space, so we'd skip it and run another iteration until the next character appears, in this case, B. They'd take an action, then get pushed ahead to a new slot. The battle would continue like this.
To implement this system, you'd do something very similar to an <a class="noExternal" href="#flow.2.a">action time with threshold</a> system. Only the threshold value and incrementor system would need to be changed. An example model is provided in the "Initiative Model: Timeline" passage.
This is a fairly intuitive system, and superb if you want a battle system based around timing and actions with variable speeds. This system meshes well with features such as charged attacks and delayed effects, as those features can be placed in tick squares as well. However, it's a lot to keep track of, and all the balance problems associated with an action time system still apply. You'll also have to come up with your own system for initial turn order, as I can't think of a good one off the top of my head. (Make a separate "initiative" stat that's either fixed for every character or can only be increased rarely, maybe?)
I took this system from <i>Exalted</i>, a fantasy tabletop RPG that's very different from the popular <i>Dungeons and Dragons</i>. <i>Exalted</i>'s system is massively more complicated than the simple model I've outlined here, but could be a good jumping-off point for you to design your own system. <i>Exalted</i> players often bemoan how hard it is to keep track of all the variables at play, but that's no problem for a computer!
<i>Exalted</i> fan Jye Nicolson created <a href="https://mengtzu.github.io/exalted/sakuya.html" target="_blank">a tutorial game</a> using JavaScript, if you would like to see how the system works in action. Because Twine operates in JavaScript, the game plays very similarly!
<h4 id="flow.3.a">Duel (One-Character) Systems</h4>
Some RPGs, or just some parts of some RPGs, forgo a party system and only give you one player character for battles. From a narrative standpoint this can be very cool: a climactic one-on-one duel with the hero's personal nemesis! But from a gameplay perspective, it often falls flat, because a big part of RPG gameplay is the synergy between multiple characters and actions. If you and the enemy are just trading blows, your options are much more limited; you (usually) can't heal and attack at the same time, for instance.
A timeline system works very well for fixing these issues. If the player can act multiple times for every action of the enemy's, you can still have tactics and synergy even with only one character. Perhaps there is a puzzle-like mechanic of matching different attacks to different enemies or guard states and you must carefully time your actions to attack and defend, almost simulating a more action-focused game? It's something worth considering if you want to use a duel system.
Noteworthy examples:
<ul>
<li><i><a href="https://store.steampowered.com/app/418190/Helens_Mysterious_Castle/" target="_blank">Helen's Mysterious Castle</a></i> is an example of an action-adventure-like system. You can choose various weapons on your turn, and each has a different speed, attack, defense value. Strategy consists of timing your attacks so that you attack when the enemy is weak and shield yourself when the enemy is attacking.</li>
<li><i><a href="https://rpgmaker.net/games/8032/" target="_blank">Red Syndrome</a></i> is a simpler example: all enemies must wait multiple turns between actions, creating a system where you have several turns to prepare for a coming onslaught.</li>
</ul>
<h3 id="flow.4">Transparency</h3>
If you use a turn ordering system, it's beneficial if you can show the turn order to the player. This will allow them to plan in advance by understanding when, say, they need to guard against an enemy's attack or when they should buff a certain character.
Doing this in a ranked order system is very simple: instead of running the selector every time a new character's turn comes up, make an array of all the characters in descending initiative order every round. You can display the array somewhere in the battle screen, and run through it as the round progresses.
Timeline systems are also fairly straightforward: you can display everyone's initiative as "ticks until next action" in their status pane or elsewhere. Ideally, with enough knowledge of CSS, you could create a visual representation showing everyone's placement in the timeline.
Action time systems are a little trickier. You'd need to simulate another round of initiative gain and use that to determine the next actor. This is both algorithmically difficult and processor-intensive to do for more than one or two predictions, so don't worry if you can't give the player an extended prognosis. This will be even tricker if you have skills that alter speed stats or initiative values, as you'll have to redo the prediction every time that happens.
Simplistic models of these preview systems are proposed in the "preview model" passages, found next to the initiative model passages. However, they are untested, and may not be suitable for all your needs. You can tweak them as necessary.
<i>Final Fantasy X</i> has an extremely robust preview system, with an extended prognosis and real-time feedback whenever an ability that would affect the turn order is used. You can try mimicking it if you -- and your processor -- are feeling ambitious!
<h2 id="progress">Progression and Leveling Up</h2>
Leveling up is traditionally one of the core features of the RPG. As you win battles or complete tasks, you will gain experience points that will eventually award you a new level, increasing your stats and giving you access to new abilities. This gives the player a sense of progress throughout the game, and also allows the designer to introduce new mechanics and tactical complexity at a gradual pace, ensuring that the player can become familiar with every one of their tools before they're given a new one. And, let's be honest -- it's just fun to watch numbers go up.
However, even if you've played RPGs before, you've probably never stopped to think about how, exactly, these features are designed. Exactly how does the game calculate how many experience points you need to get to the next level? Exactly how does the game calculate how to increase your stats on a level up?
As with everything in this list, the answer is, "It depends on what experience you, the designer, want to cultivate!" However, that's easier said than done. Progression mechanics are some of the hardest things to design and balance, because they have impacts throughout the entire game. You should also understand that leveling up isn't the only way to accomplish this! We'll discuss alternative progression methods as well, such as point-buy and equipment-based systems.
You may notice that <i>Cartoon Battle</i> doesn't feature any progression mechanics. That's because implementing and balancing this is actually very tricky! You will need to plan very far ahead, and balance your game not only for one state but for situations where the player's capabilities will change over time.
For further reading, see the video "<a href="https://www.youtube.com/watch?v=Ae_zvm0-CQU&list=PL8K0_g1wdQepKF0a8eh_4dhhivcSrSKyO&index=20" target="_blank">What Makes a Good Level Up System?</a>" by Design Doc, which provides another designer's opinions on the progression systems discussed here.
<h3 id="progress.levels">Levels</h3>
We'll start with the one you're likely most familiar with: Characters gain experience points, or XP, from succeeding at gameplay challenges (often killing monsters, but possibly also solving puzzles, navigating conversations, or completing quests). When a character's XP reaches a certain value, they level up and gain increments to various stats. They may gain new abilities as well, but <a class="noExternal" href="#progress.skills">that is another discussion entirely</a>.
Predetermined levels, where the player has no control over the bonuses of a level up, are common in Japanese-style RPGs such as <i>Dragon Quest</i>. (Western RPGs such as <i>Baldur's Gate</i> tend to favor a more open-ended format where levels will give the player some choice over their desired bonus, but that is discussed under <a class="noExternal" href="#progress.pointbuy">point-buy systems</a>.) This method requires the least engagement from the player, as most of the decisions -- when their characters will get stronger and by how much -- are made for them, with all the pros and cons that implies. This method is probably the easiest to balance, as it is easy to predict a player's capability at a given point in the game when their progression is fixed. However, it also means you'll have to plan out every character's progression yourself, which can be a big task!
You should be able to identify two important design questions here: <b>How much XP do characters need to level up?</b> and <b>What are the exact benefits of a level up?</b> There is a potential third question, related to the first, which is what situations will provide XP and how much.
<h4 id="progress.levels.1">Calculating XP Requirements</h4>
You could, in theory, design unique XP requirement values for every single level. However, this is not what most games do, and it is not recommended. It's much easier to design a formula that will generate XP requirements for every level automatically.
The simplest scenario would be no variable formula at all: every level requires the same amount of XP. However, this doesn't make much sense as a design principle. If you're guaranteed a level up every time you, say, kill 10 of the enemies in the starting area, yet each level makes you stronger, you'll kill enemies faster and gain levels faster with each level up, all without even needing to leave the starting area. This model is sometimes viable in certain RPGs with "elastic" difficulty, where enemies have their own levels and become stronger with the player characters, maintaining the same relative difficulty no matter the player's level. However, this is rather complicated to implement, and not the playstyle most RPGs aim for.
You might think the best method would be a linear scale: If going from level 1 to level 2 requires 1000 XP, then from 2 to 3 requires 2000 and from 3 to 4 requires 3000, etc. This can work, but it still tends to make leveling too rapid and players too complacent. To really force players to move on to harder challenges, you need an <b>exponential</b> scale, meaning the difference <i>of</i> the difference in XP requirements becomes greater with every level.
As an example, here is the XP formula for <i>Dungeons and Dragons 3rd Edition</i>:
<div class="formula">XP to next level = 500 * (level ^ 2) - 500 * level</div>
(Technically, this function is quadratic, not exponential, but the principle still applies.)
Here's what that looks like graphically:
[img[setup.ImagePath + "documentation/levels1.PNG"]]
Notice that this growth curve is extremely steep; we can't even see the XP requirement for the maximum level, 20, without extending the Y-axis. After only 4 levels, the XP requirement is 10 times that of level 2.
This is because, in <i>Dungeons & Dragons</i>, every level is intended as a rare and major accomplishment. You might only gain one or two levels during a "chapter" of the story, and you're unlikely to go all the way from level 1 to level 20 in anything but the longest campaigns. <i>Dungeons & Dragons</i> also provides the bulk of its XP rewards through major, one-time accomplishments, such as completing quests, rather than many repetitive actions such as killing individual monsters. A steep formula such as this encourages players to focus on big actions such as pursuing side quests, and rewards close engagement with the world to find such opportunities.
You tend to see steep formulas like these in Western RPGs, which are more directly based on <i>Dungeons & Dragons</i>, than in Japanese-style RPGs such as <i>Dragon Quest</i>. Twine's structure is actually well-suited to the style of a Western RPG, so this formula may be worth considering.
On the opposite end of the scale, we have the "medium fast" XP formula from <i>Pokemon</i>. Different Pokemon use different formulas, but this one is the most common:
<div class="formula">XP to next level = (level ^ 3)</div>
Or graphically:
[img[setup.ImagePath + "documentation/levels2.PNG"]]
This curve is <i>much</i> shallower. While the first formula exceeded our graph's bounds almost immediately, we can fit 20 levels on this scale no problem. (There's no trickery here: the axes have the same scaling as before.)
The reason for this is quite simple: In <i>Pokemon</i>, level ups are meant to be frequent. Not only is the max level (100) much higher than in D&D, but as a monster-collecting game, you are encouraged to raise a large number of "party members". Players are supposed to vary their team and swap out Pokemon over the course of the game. To incentivize this, the game has to make it easy for lower-level Pokemon to catch up if they've been off the team for a while. Thus, low XP requirements.
Shallow formulas are common in Japanese-style RPGs, which tend to make levels more frequent but less individually significant. This incentivizes the player to pursue many small-scale actions that result in incremental rewards, rather than big jumps. You should pick this formula if you want a "grindy" game designed around repetitive actions with frequent payouts.
You don't strictly need a formula at all: it's also possible to use a lookup table with unique values for every single level. But a formula is probably a good starting point if you're new to designing these mechanics.
It should be noted that this is all a <i>gross</i> oversimplification that only scratches the surface of this topic. Things get vastly more complicated if you want more nuance, such as different leveling rates for different characters (older versions of D&D did this, making wizards require more XP per level because they got more out of levels), or elastic leveling where the difference between the hero and enemy's levels affects the payout (useful for discouraging grinding and catching up low-level characters). You'll need to ask around (and learn some math) if you want to tweak your leveling mechanics for something particular. I'd recommend starting by looking up the mechanics from your favorite games -- famous ones such as <i>Dragon Quest</i>, <i>Pokemon</i>, and <i>Dungeons & Dragons</i> are usually well-documented. Break them down by thinking about what you liked and disliked about them, and work from there.
<h4 id="progress.levels.2">What Do Levels Do?</h4>
So say a character has leveled up. What happens now, exactly? Generally, the character's stats are supposed to increase. But by how much?
Some games make stat gains random. I hate this with the intensity of a thousand burning suns and will speak no more of it here. Go to <a href="http://howtomakeanrpg.com/a/how-to-make-an-rpg-levels.html">How to Make an RPG</a> if you wish to pursue this heathen practice.
If you wish to be <i>sensible</i> and make your stat gains totally deterministic, then like XP gain, there are two ways to do this: either through a formula, or by manually setting stats for every level through a lookup table. To effectively design the latter you will need to understand the former, so that's what I will discuss.
The principles for designing a stat growth formula will depend, obviously, on what those stats do and the progression you want to see over the course of the game. In some games, even a single point of a stat can be significant, while in others, stats only have a noticeable effect at high values. You're going to want to review your damage formula and anything else stats might affect, such as status effects and <a class="noExternal" href="#flow">turn order</a>. There is also the matter of "power creep", discussed in the damage formula section: you likely want things to get bigger and flashier as the game progresses, but by how much? Do you want average attack damage to increase at a gradual, linear rate, or at an exponential one that makes the endgame feel like a totally different world than the beginning? This will also be influenced by the XP gain model you're using: if levels are rare, you will probably want them to provide big gains, while if levels are frequent, you will probably want smaller gains per level.
If you want to preserve character specializations, you'll also want these formulas to be different for different characters. A wizard should probably gain Defense more slowly than Magic, for instance.
RPG Maker has default formulas for calculating stat growth of various character archetypes, though I cannot find good documentation for this. If you would like to devise your own, I recommend starting with a small-scale game where each stat point matters, and making stat gains very small. This will be easier to test and balance, as even small changes will have highly visible effects. Learning from example is harder here, as most commercial RPGs use random stat gains. If it works for them and you don't want to bother with complex math you could consider trying it I GUESS.
More atypical arrangements have been devised by some games. You could make stat growth dependent on one or more other variables, as seen with <i>Pokemon</i>'s IV and EV values. (That's detailed <a href="https://bulbapedia.bulbagarden.net/wiki/Statistic#Determination_of_stats" target="_blank">here</a>, for the curious.) You could also decrease the influence of stats by making level a factor in gameplay formulas that use stats, maybe only increasing stats rarely or through other means. This is particularly useful for player-determined stat growth systems, as it gives you a general power level to expect for balance testing, even if the player uses a build you hadn't thought of.
<h4 id="progress.levels.code">Implementation</h4>
Skeleton code for levels and growth tables are included but not detailed in the story JavaScript at the end of the "Puppet" class definition. To actually implement a level up feature, you'd create a widget or another passage that checks the character's current XP against the next level's requirement, and run it whenever a character gets XP. (To save yourself time, you could even make it part of the same code that gives a character XP.) Within this function, you'd determine the stat gains through your formulas or through a lookup table, and do the same to find the XP requirement for the next level.
(A word of warning if you're using a lookup table: while the most intuitive method is to have the lookup value correspond to the <i>absolute</i> stat values you want at that level, if you have events that change base stats through means other than leveling, the lookup table will overwrite those changes! You will have to either store those changes through a separate value, or make the lookup table point to increments instead of absolute values.)
Example widgets for this implementation are provided in the "Widgets: Leveling Up" passage.
<h3 id="progress.pointbuy">Point-Buy</h3>
This is an alternate form of stat progression used by several RPGs, including famous franchises like <i>Shin Megami Tensei</i> and tabletop RPGs like <i>Exalted</i>. Instead of stats being automatically incremented at fixed rates, the player is given control over which attributes they want to improve. You can combine this with a regular leveling system, with every level up giving one or more stat points, or allow players to spend XP on stat upgrades directly.
(If you do the former, however, it's recommended you don't make the number of points per level up divisible by the total number of stats. A lot of players will want to give the same amount of points to all stats just because that's the simplest option. You'll get more engagement if you force them to make a choice.)
This system is popular for the freedom it gives to players, but it can be very difficult to balance due to the many stat arrangements possible as the game progresses. If you design challenges assuming players will add points in a specific arrangement and then a specific player creates a build you didn't expect, the player could have a very hard time. You'll need to do a lot of balance testing to ensure it isn't possible for the player to push themselves into a corner. Ideally, no build should be universally better than another; the player should be allowed to choose a build based on their preferred playstyle, not bite their nails over if they've made the objectively best option.
Some games that use this system allow the player to undo their investments and reconfigure their build. This eliminates the possibility of locking the player into a non-viable build, but potentially eliminates some strategic depth as well. It can be a purposeful design decision to make certain builds more optimal in certain situations and less optimal in others; if the player can just respec to find the optimal build in every situation, the game might become too easy or too uniform. However, you may like this idea as a "puzzle" aspect, forcing the player to think about the optimal allocation of their resources for difficult challenges.
One possible drawback to this system is the elimination of character specialties. Characters in RPGs typically have differing stat layouts that lead to different roles. However, if the player has total control over characters' stat layouts, these specialties can disappear; there's nothing stopping them from putting all the wizard's points into Strength or all the fighter's into Intelligence. Sometimes it can be fun to see how these non-standard builds play out, but sometimes you do want to keep a role fixed. You could enforce this through a "soft" method by giving characters or classes fixed capabilities that draw on specific stats, encouraging players to invest in certain stats over others. (If a fighter can't use magic, for instance, there's no reason to increase their Magic Attack even if you technically can.) Alternatively, you could use a "hard" method of making certain stat improvements cost more for certain characters. It might actually be a good idea to pump up the wizard's Defense, but if that costs twice as many stat points as improving Magic, you won't be able to do it as often. Alternatively, you could make stat improvements small compared to initial stats -- this makes it hard for low stats to "catch up" with high ones even if there is no cost weighting.
The other thing you'll need to watch out for is whether or not it's possible to get everyone to perfect stats eventually. If everyone can be fully maxed out, that's the end goal players will shoot for -- and at that point, everyone's the same! (<i>Final Fantasy X</i> famously had this issue, where the advancement grid was shared by everyone, allowing characters to gain the exact same stats and skills as other characters if they leveled up enough.) It's easy enough to fix this if you don't want it -- as you'd define a level cap in a leveling system, you can create a cap on either stat totals or number of stat purchases -- though you'll probably want to make this clear to the player! You could also make experience points a limited resource.
This system has a lot of potential variations, so it's worth looking at lots of different games that use it. You can have fun tradeoff mechanics by making "stat experience" useful for other things as well -- most commonly, the mechanic is framed as paying a trainer to improve your skills, which means you have to choose between items and stat improvement when deciding how to spend your money.
<h4 id="progress.pointbuy.code">Implementation</h4>
Combining this feature with a level up system is easy: just add a "stat point" attribute to the Puppet class and increment it on level up. Then you just have to add another passage/menu where players can spend them. (Note that you will need to save the total points accumulated in a separate attribute from the character's spendable points if you want to provide a respec option.)
The passage "Point-buy interface example" provides an example of how you might implement this in a menu.
A "free buy" system is a little trickier, but is fundamentally similar to the code used to calculate XP requirements for levels: you would create a widget or JavaScript function to calculate the XP cost, and pass it the necessary variables. It's recommended you make XP costs increase at higher stat values, just as levels have increasing XP requirements. You could make every improvement increase the cost of all future improvements, or make it a function of the current stat only. The latter method disincentivizes overspecialization and encourages players to spread out their improvements. As with a stat-points-by-level-up system, you would need to record total XP separately if you want to enable a respec system. You might also want to make the cost growth dependent on <i>upgraded</i> rather than <i>total</i> stats, which would necessitate another variable for storage.
An example of such a calculation function is provided in the story JavaScript as a method function of the Puppet class.
<i>Bonfire</i> has an example of a point-buy system: though heroes level up within individual game stages, they restart at level 1 whenever they start a new stage. The only way to permanently improve their stats is to buy upgrades with gold accumulated from journeys. A chart of the upgrade costs is <a href="http://bonfire-game.wikia.com/wiki/Upgrading_Heroes" target="_blank">here</a>, though no clear function has been derived; it is likely a complex piecewise function, or a lookup table of unique values.
<h3 id="progress.equip">Equipment-Based Progression</h3>
Most RPGs feature an equipment system of some kind in addition to levels. In effect, this can create two simultaneous paths of power progression: you can improve your abilities both by leveling up and by buying better equipment.
I mentioned in [[the design page|Design]] that I tend not to like this model, as it feels redundant: not only does the player usually improve equipment through the exact same methods as you improve levels -- killing monsters and doing quests usually give you both XP and money -- but equipment often conveys the same benefits as level ups (stat boosts, more damage, etc.). What this means is that not only are you never forced to choose between levels and equipment, there wouldn't be much significance to the choice even if you were.
To avoid this problem, I recommend giving equipment and levels categorically distinct contributions to the character. They shouldn't improve power in exactly the same way all the time. This can be seen in <i>Dungeons & Dragons</i>, where levels tend to grant qualitative changes -- additional actions to take, bonus to circumstantial situations, etc. -- while equipment is one of the only ways to obtain quantitative improvements, such as better damage and armor. This can make choosing between gold and XP a meaningful choice that forces the player to think about exactly how they want to improve their character. That kind of engagement typically makes for a more enjoyable experience, so it's something you should shoot for.
Some games go even farther, and make equipment the <i>only</i> method of power progression. Under this model, there are no levels or inherent bonuses: all your growth comes from finding better equipment. Want stronger attacks? Get a better weapon, or you're out of luck. This can create an interesting effect where your characters are completely modular, especially if you make equipment govern <i>actions</i> as well. At any point, you can completely reinvent a character, swapping their capabilities with another. Such games typically give characters some unique stat or ability differences to avoid making them completely interchangeable, but the system will still be highly mutable.
One potential downside of this system is that it can remove the incentives from normal gameplay, if you adopt a jRPG model of many mundane, grindy tasks. Even if you reward players with money with which to buy more equipment, players will typically hit a ceiling very quickly and find themselves with nothing to spend it on until they reach the next area with better equipment. This can shatter the Skinner-box action-to-reward mentality that many games run on, and leave players feeling bored and frustrated. On the other hand, this may be better for more narrative-driven experiences that revolve around a small number of set-piece challenges.
Additionally, this method must be balanced more carefully than usual, due to something obvious but often overlooked: If players start with no equipment at all, they will hit what feels like a ceiling as soon as they fill all their slots. Where before everything was an absolute improvement, now they must make tradeoffs to progress further. You will have to structure your game carefully such that equipment continues to improve at a consistent rate, and that this is communicated to the player. For this reason, this system tends to be better suited to shorter games where this issue will not become as pronounced.
Narratively, this is an effective way to convey a sense of realism. In real life, people don't just magically get tougher, stronger, and faster by being really good at something; the skills obtained through training and practice tend to pale in comparison to the advantages of getting better weapons and technology. Outsourcing all the player's power to something else reminds them of that fragility, especially if you do something cruel like letting enemies steal or destroy their items. This can be very good for an RPG in a modern setting, or one with a strong focus on technology.
Notable examples:
* <i><a href="https://rpgmaker.net/games/5904/" target="_blank">Czarina Must Die!</a></i> tends to run into the problems I outlined towards the end; it unfortunately uses random encounters <i>as well as</i> quickly making equipment purchased with money obsolete, giving the player little incentive to fight monsters.
* <i><a href="https://rpgmaker.net/games/4526/" target="_blank">Wine & Roses</a></i> and some of the other games in the same "genre" by the same developer use equipment for both stat and skill advancement. Inherent bonuses sometimes feature as well, but they are rare and minor in comparison.
* <i><a href="https://rpgmaker.net/games/5132/" target="_blank">In Search of Immortality</a></i> uses a very literal approach: all equipment is just literally a stat boost, with stronger boosts dropped by monsters as you progress.
<h3 id="progress.statgrind">Stat Grinding</h3>
There is another progression method known as "activity-based leveling" or, more colloquially, "stat grinding". The premise of stat grinding is that, unlike with experience levels, which give you across-the-board improvements at intervals, you improve your stats or skills individually by performing actions related to them. For example, attacking with a weapon may improve your skill with that weapon type, or getting hit might improve your defense score.
This approach can feel a lot more "realistic" than experience levels, as it's much closer to how you improve skills in real life. It can also provide more organic gameplay and character progression, as characters will naturally get better at whatever playstyle you prefer to use for them. It can be fun to start with a party of very similar "blank slate" characters and watch them diverge based on how you use them, and also adds replay value when every character has so many potential builds. (Note that you don't <i>need</i> for every character to start the same, though! Especially in multi-character setups, you can still encourage growth in one direction or another by making it easier or harder for certain characters to improve skills.)
In practice, however, this approach has a lot of problems and pitfalls. Optimum play under this system often turns out to be unintutive and highly bizarre. An infamous example is from <i>Finaly Fantasy II</i>, where players were incentivized to attack their own characters to gain HP bonuses. Depending on how broad the system and statistics are, this can also encourage tedious and repetitive actions; another infamous example is players constantly jumping to improve their acrobatics skill in <i>The Elder Scrolls</i>. Even aside from this, issues of scale crop up: How does a developer encourage the player to move on to stronger enemies, if the rewards of battle are based on the player's actions and not the difficulty of the encounter? Experience levels can easily deal with this problem by progressively increasing experience requirements and rewards, but with stat grinding the solutions are less obvious.
One solution is indeed to make stats and skills function just like levels, with increasing experience requirements for each improvement. This is the method used by <i>The Elder Scrolls</i> series. However, the details of this can be complicated. Do you want different stats to have different experience curves? What about different gain rates? How do you determine how much experience to give per action? If you keep the hit-to-kill ratio constant throughout the game -- as you probably should -- then surely stat improvement will slow down as the player takes the same number of actions per battle but requires more actions to level up? <a class="noExternal" href="#progress.levels.1">All the questions and calculations you have to make for experience levels</a> will now have to be made for <i>every single stat</i>. This can be a daunting task!
Another method is to still use levels behind the scenes. This is the method used by <i>Final Fantasy II</i>: Every enemy is assigned a hidden "rank" statistic that is added to the experience points awarded for every action in battle. The experience reward is, in turn, <i>decreased</i> by the rank of the skill used (weapon or spell), requiring the player to fight stronger enemies to improve their skills. In this system, every skill level requires the same amount of experience to improve; it is the experience per action that changes to create a progression curve.
If you plan to use this system, I recommend looking at examples of games that have tried it before. Observe their strengths, pitfalls, and different methods of implementation. This is a very delicate and finnicky system, and will require a lot of care to balance properly. One area in particular I'd recommend looking into is the problem of defensive stats: Stat grinding works well for actions that the player can make directly, such as attacks, but invites difficult questions when it comes to passive abilities. Unless you use something like [[an aggro system|Design]], the player has no control over who gets hit by attacks -- unless, of course, they attack their own characters, which is nonsensical. And even if you do have an aggro system, placing fragile characters away from danger can actually be a bad idea, as it prevents them from ever getting tougher, leaving them increasingly vulnerable to area attacks and the like. (This was another infamous problem with <i>Final Fantasy II</i>, and in large part why the friendly fire method of training was necessary.) <i>The Elder Scolls</i> sidesteps this issue by divorcing HP gain from the standard stat grinding model and instead granting the player an automatic bonus after they have improved enough skills; I would recommend doing something similar.
Notable examples:
* <i>The Elder Scrolls</i> is one of the most famous RPGs of this type. Though the very first game used standard experience levels, later games used a stat grinding system based around gameplay-relevant skills. The series has extensive fan documentation via <a href="http://en.uesp.net/wiki/Main_Page" target="_blank">its wiki</a>, with pages detailing the advancement mechanics for the <a href="http://en.uesp.net/wiki/Daggerfall:Leveling_and_Skills" target="_blank">second</a>, <a href="http://en.uesp.net/wiki/Morrowind:Skills" target="_blank">third</a>, <a href="http://en.uesp.net/wiki/Oblivion:Increasing_Skills" target="_blank">fourth</a>, and <a href="http://en.uesp.net/wiki/Skyrim:Leveling" target="_blank">fifth</a> games in the series.
* <i>Final Fantasy II</i> and its spritual successor, the <i>SaGa</i> series, are famous jRPG examples of this type. The mechanics are documented fairly comprehensively <a href="https://guides.gamercorner.net/ffii/systems/" target="_blank">here</a>. Note that statistic improvements were very different (and much simpler) than skill and spell improvements, with actions simply giving the character a random chance of improving the stat by 1 point.
* <i>Pokemon</i>, though primarily an experience level system, uses a form of this. Pokemon have an "Effort Value" (sometimes called "stat experience" by players) associated with each stat that is used to calculate the effective stat through <a href="https://bulbapedia.bulbagarden.net/wiki/Statistic#Formula" target="_blank">a complicated formula</a>. In addition to each stat having a maximum possible Effort Value, the <i>total</i> Effort Value points a Pokemon can have is also limited, creating a specialized distribution of stats. However, unlike most stat grinding systems, stat experience is gained not by performing relevant actions, but by defeating enemy Pokemon. Like many mechanics in <i>Pokemon</i>, almost none of this is explained to the player in the game and the whole system is very opaque, a fact that has generated much frustration among players. Don't be like <i>Pokemon</i>: be clear and transparent if you use a system like this.
<h3 id="progress.fixed">Fixed or Story-Based Progression</h3>
Some games take the control out of the player's hands, and provide improvements only at fixed points in the game. You may only gain a new ability or stat boost after beating a boss, reaching the next area, or progressing the story.
This is a very direct way of implementing a progression system, but also a bare-bones one. A big benefit of an experience and leveling system is that the player will always gain a tangible reward for engaging with the game; take that away, and they may just feel like the game's challenges are a waste of time. (Of course, you should be making your game intrinsically rewarding to play <i>anyway</i> -- if you can kill a player's motivation by taking away an extrinsic reward, you're not making a very fun game.) It does, however, give the designer immense control over the player's experience. You don't have to worry about a player being over- or underleveled, or picking a weird build; you know exactly what state they'll be in going into every battle. Used well, this method can therefore be used to craft tighter and more complex challenges, and to ensure no player is left behind.
Some games will mix this concept with more standard progression systems. For instance, the game may use a leveling system for most of its mechanics, but the player may also be given a special ability for reaching a certain point in the story. You can also strike a compromise of still allowing the player control over their progression by making experience points a limited resource only awarded at certain benchmarks in the game.
Equipment-based progression is often a subtle form of this, as better equipment usually can't be obtained until the player progresses in the game.
This method can work well for narrative-heavy RPGs that revolve around a small number of set-piece battles rather than many less significant encounters, and can even be used to tie the gameplay and story together by justifying the characters' improvement through in-story beats. I also recommend some form of this for RPGs with puzzle-like encounters that are balanced assuming the player has access to a specific ability.
Notable examples:
* <i><a href="https://rpgmaker.net/games/6554/" target="_blank">Null Regrets</a></i> and <i><a href="https://laburatory.itch.io/lgd" target="_blank">Long Gone Days</a></i> both eschew levels entirely and award new skills only after clearing chapters of the story. However, this has led to the criticism that players feel little point in fighting non-critical battles.
* Games from the developer Craze, such as <a href="https://rpgmaker.net/games/4526/" target="_blank">Wine & Roses</a>, use a non-linear form of this. There is no leveling system, but every battle rewards the player with a specific new ability or powerup. The choice of what fights to tackle and when give the player immense control over their progression despite its fixed nature, making the game feel similar to a Metroidvania.
* <i><a href="https://en.wikipedia.org/wiki/Torment:_Tides_of_Numenera" target="_blank">Torment: Tides of Numenera</a></i> makes experience points a limited resource. They are primarily gained by completing quests and advancing the story, but players can also gain a few points by finding secrets in the world. You can comfortably reach the max level if you are diligent at completing quests, but your specific capabilities are determined by the choices you made at each level up.
<h3 id="progress.skills">Skill Progression</h3>
Most RPGs don't just improve characters' stats as the game progresses; they provide the player with new abilities as well. Fighters gain new feats such as Power Attack, wizards gain new spells, rogues gain new skill points, and so on. This is crucial for keeping the player engaged, as it makes the game and the player's experience evolve in complexity over time.
However, there is the question of how fast you introduce new abilities, what abilities you introduce, and if you want a creeping power curve of stronger abilities over time. If you give the player too many abilities too quickly, they will be overwhelmed, and individual abilities won't have time to shine. If you dole out abilities too slowly, players may lose interest and find the game repetitive.
You should generally gate more complex abilities behind later progression markers. This is why many RPGs either start you with fighter-type characters or with only a few basic spells. The player needs time to master the basic mechanics of the game before they can be introduced to new twists like limited resources, elemental weaknesses, and status effects. In turn, give the player time to get used to each new mechanic before introducing them to another.
One particular quirk of skill progression, especially in jRPGs, is that mage-type characters tend to have their effectiveness tied to specific skills more than their stats. You might start the game with access to Fire 1, then later, you learn Fire 2, which is identical in function but stronger. This is most easily handled by pairing a constant damage value with a smaller stat damper (see the damage formula section in [[Design]]), such as <i>C + (A/2) * a.magic - B * b.mdf</i>, where A is the stat coefficient for regular attacks. A large <i>C</i> constant will make a spell very powerful initially, but it will wane in usefulness as enemies gain more HP, and the smaller stat coefficient on Magic Attack will prevent stats from making up the difference. In comes Fire 2, with the same formula but a larger constant, and your mage is back in power... until enemies get stronger again.
This method is reasonable when magic functions as a limited resource. Typically, characters gain more magic points or spell charges as they level up, so you become able to use the same spells more often. It wouldn't make sense for a spell you can use 3 times to be just as effective when you can use it 6 times, as that would trivialize battles. You need a way for spells to wane in effectiveness as they become cheaper to the player.
However, this approach is widely disliked by modern gamers, because it leads to a lot of spells that you'll never use after a certain point. Once you get Fire 2, there's no reason to use Fire 1 again -- it's just a useless entry taking up space in your skill menu. Western RPGs based on <i>Dungeons & Dragons</i> avoid this problem by allowing all spells to keep up with the caster's power level -- even basic spells will gain bonuses as you level up, keeping them competitive even at higher levels. However, this can lead to a new problem of making mages overpowered, because they're improving their old abilities <i>while also</i> gaining new abilities at the same time, leading to exponential power growth.
The tiered spell approach is effective, but it's also lazy. I encourage you to come up with more creative solutions. Maybe <i>don't</i> let the player increase their MP, keeping their spells-per-dungeon ratio constant throughout the game? Maybe make dungeons longer or harder, necessitating the use of more spells even though they're just as useful throughout? You could also try making skill upgrades purely qualitative, rather than quantitative -- a new option like a status effect or another element, but nothing strictly better than what the player already has access to.
<h2 id="battlegrid">Battle Grid</h2>
In the default engine, the placement and order of characters in a party doesn't matter. You can stack as many characters into the party as you want, but they'll all exist in a nebulous space where everyone is equally vulnerable to attacks and every enemy is in reach.
This is a nicely simple model, but not all RPGs use it. Some RPGs make space and positioning matter.
You're probably most familiar with this in the form of "tactical RPGs" like <i>Final Fantasy Tactics</i> or <i>Fire Emblem</i>, where battles take place on huge maps, characters can move a limited distance per turn, and attack range may be limited. Something <i>that</i> complex is beyond the scope of the engine at the moment (though you may be interested in <a href="https://sinisterdesign.net/products/telepath-tactics/" target="_blank">Telepath Tactics</a>), but something of more moderate complexity can be enabled with the {{{BATTLE_GRID}}} variable.
<h3 id="battlegrid.1">Setup</h3>
This is gonna get complicated, because it involves <i>\*shudder\*</i> graphic design. We're going to have to first build a graphical representation of the grid for our webpage. Let's look at how that's done in the {{{battle grid.css}}} file:
{{{
.actors.grid {
display: grid;
justify-items: center;
align-items: center;
row-gap: 1em;
width: 100%;
height: auto;
}
.actors.grid.large {
grid-template: repeat(2,1fr) / repeat(3,1fr);
}
.actor.grid {
position: relative;
width: 140px;
min-height: 96px;
}
.actor.large {
grid-column: 1 / 4;
grid-row: 1 / 3;
width: 100%;
height: 192px;
padding: 0;
display: flex;
flex-direction: column;
text-align: center;
justify-content: center;
align-items: center;
}
}}}
You may recall from the actor setup notes in [[Documentation|Documentation (Advanced)]] that we added "grid" classes to the actors and their containers when {{{BATTLE_GRID}}} was enabled. Here, you can see how that class changes the display.
{{{
.actor.grid {
position: relative;
width: 140px;
min-height: 96px;
}
}}}
We also need to modify the {{{actor}}} elements. This map is much more rigid than the standard display, so we can't have actor boxes stretching or shrinking based on their content; we need to fix them to a set width. <b>The width of these elements and the width of the {{{#content}}} element are going to determine how many columns your battle grid can span.</b> By default, you can comfortably fit only 3 columns into the 640px-wide {{{#content}}} area. (You may do the math and think that <code>640 / 140 =</code> 4 columns, but that's not accounting for padding and other spacing that's included in these elements.) If you want more columns, you will need to either expand the width of {{{#content}}} or shrink the size of the {{{actor}}} boxes.
The {{{large}}} class is the battle grid's equivalent of the {{{full}}} class in the normal display, a way to make enemy boxes bigger and more impressive-looking. By default, this is set to a defined size: 3 columns and 2 rows, spanning the full width of the container and the height of two actor boxes. Note that this assumes "large" enemies are the only characters on their map, as the class modifies the {{{actors}}} container itself. You may adjust this to suit your needs.
Savvy readers may notice at this point that we haven't set any dimensions (rows and columns) for our grid yet. The reason for that is because we want the grid to be able to shrink and expand to suit the user's needs, rather than fixing it to one set of dimensions. To define the dimensions, we go to {{{user storyinit}}}:
{{{
<<if setup.BATTLE_GRID === true>>
<<set setup.ROW_SIZE = 3>>
<<set setup.COLUMN_SIZE = 3>>
<<set setup.PARTY_SIZE = setup.ROW_SIZE * setup.COLUMN_SIZE>>
<<set setup.STATUS_SCREENS.menu.push("Formation")>>
<</if>>
}}}
When you enable the battle grid, you also need to define the length of your rows and columns. By default, that's set to 3 each, the maximum width that can fit comfortably in the default display. These variables will be used for several functions relating to the grid, including the construction of the grid itself.
Let's look at how to set up the grid in {{{actorlist}}}.
{{{
<div @class=_enemiesClass>
<<if _enemy !== null>>
(...)
<<else>>
/* no content; empty actor box */
<</if>>
</div>
}}}
Everything we learned about populating the actor boxes in [[Documentation|Documentation (Advanced)]] happens within the first branch, {{{if _enemy !== null}}}. Else, if the character <i>is</i> {{{null}}}, we do nothing: we leave the box empty. Because we still wrapped this tree in a {{{div class="actor"}}}, the actor box will still generate and be slotted into the grid, but it will be blank, representing an empty tile.
In other words, <b>we use {{{null}}} entries in party arrays to denote unoccupied tiles in the battle grid. This means you must include empty tiles as well as characters to the party arrays.</b> This is done to maintain a consistent size for the battle grid, enforced by the {{{PARTY_SIZE}}} variable.
Note that <b>{{{null}}} values do not have properties and will generate an error if you try to read them as if they are objects</b>, so you must be careful to filter or provide handlers for {{{null}}} entries every time you iterate over a party array. This is the purpose of the {{{enemies()}}} and {{{puppets()}}} functions, which return their respective party arrays with {{{null}}} entries excluded.
...But we still haven't defined our grid dimensions! That comes at the end of the passage, here:
{{{
<<timed 0s>>
<<script>>
$("#puppets.actors.grid").css({
"grid-template-columns": `repeat(${setup.ROW_SIZE},1fr)`
});
<</script>>
<</timed>>
}}}
This code runs raw JavaScript code. This code finds the {{{actors}}} container using jQuery, then modifies its {{{grid-template-columns}}} attribute to define a number of equally-spaced columns equal to {{{ROW_SIZE}}}. (We have to place it in the seemingly-pointless {{{<<timed 0s>>}}} macro because elements do not exist on the page untill all code has run and the passage has finished rendering. If we tried executing this code outside of a {{{<<timed>>}}}, it would tell use the element doesn't exist. But code within {{{<<timed>>}}} blocks execute after the passage has rendered, so with a delay of 0 we can use them to instantly modify an element on the page just after it's rendered. See <a href="http://www.motoslave.net/sugarcube/2/docs/#macros-dom-warning" target="_blank">the SugarCube documentation</a> for more info.)
Note that the selector specifically only targets elements with the {{{grid}}} class, so this code won't do anything if the battle grid isn't enabled. For efficiency, though, we still wrap it in a check that only executes if {{{BATTLE_GRID}}} is enabled.
{{{
<<run reverseChildren(document.getElementById("enemies"))>>
}}}
Enemies require an additional step. The grids here are populated according to the automatic fill rules, which means their tiles will be populated left to right, then top to bottom. This works great for the player's party... but the standard battle interface is designed to look like the enemy party mirrors the player. The enemies should therefore fill the grid bottom to top, with the front row on the bottom and the backmost row on the top. That's the purpose of this {{{reverseChildren}}} function, defined in {{{1_support-functions.js}}}. It will completely reverse the appearance of the enemy grid, with the 0th-indexed enemy appearing in the bottom-right and the nth-indexed enemy appearing in the top-left.
In effect, most of the work has been done for you here. You only need to plug in values for {{{ROW_SIZE}}} and {{{COLUMN_SIZE}}}, and extend the width of {{{#content}}} if you want wider battle grids... <i>if</i> you restrict yourself to perfectly rectangular battle grids. If you want an uneven grid with protrusions or holes, that's going to take more work. You will likely need to define both a custom grid and custom placement for your actors (via the {{{grid-rows}}} and {{{grid-columns}}} attributes).
For more information on how to use grids, see <a href="https://css-tricks.com/snippets/css/complete-guide-grid/" target="_blank">this guide by CSS-Tricks</a>.
<h3 id="battlegrid.2">Features</h3>
So now that we have our grid, how do we use it for gameplay?
You can potentially use the grid for a lot of things. By default, the only thing it does is allow characters in higher rows to guard lower ones.
{{{
window.guardCheck = function guardCheck (index) {
if (index < 0 || index >= V().enemies.length) {
console.log("ERROR in guardCheck: index out of bounds");
index = Math.clamp(index,0,V().enemies.length-1);
}
if (setup.BATTLE_GRID != true) {
return true;
}
else if (index >= setup.ROW_SIZE && V().enemies[index-setup.ROW_SIZE] !== null && !V().enemies[index-setup.ROW_SIZE].dead) {
// Guarded by character in next row up, cannot be targeted
return false;
}
else if (index >= setup.ROW_SIZE * 2) {
// No immediate guard (previous check failed), but not in front two rows, so another guard is possible. Move up a row and check again.
return guardCheck(index-setup.ROW_SIZE);
}
else {
return true;
}
}
}}}
This function, defined in {{{1_support-functions.js}}}, runs that logic for us. Pass it the index value of an enemy, and it will check to see if there's a live enemy in the next row. If there is, we return {{{false}}} to show that this enemy is guarded; otherwise, we move up a row and check again; and if there's still no guard, we return {{{true}}}. (And if this function is called with the battle grid inactive, we just always return {{{true}}}.)
{{{
<<if (!_martyr || _enemy.martyr) && (_enemy.martyr || !_enemy.untargetable) && ($action.ranged || guardCheck(_i))>>
}}}
You can see here (in {{{actorlist enemies}}}) that this function is used in the targeting phase. The enemy must pass the {{{guardCheck}}} for their name to be targetable -- though the check can be bypassed with a ranged attack.
{{{
<<widget "guardCheck">>
<<set _index = _party.indexOf(target())>>
<<if _index >= setup.ROW_SIZE && _party[_index-setup.ROW_SIZE] !== null && !_party[_index-setup.ROW_SIZE].dead>>
<<set $B.target = _party[_index-setup.ROW_SIZE]>>
<<elseif _index >= setup.ROW_SIZE * 2>>
<<set $B.target = _party[_index-setup.ROW_SIZE]>>
<<guardCheck>> /* Run again to test if the new mid-row target is in turn guarded by a front row character */
<</if>>
<</widget>>
}}}
We run analagous code in the targeting widget. The difference here is that the check can be run on any party, and we set a new target instead of returning {{{false}}}.
{{{
<<elseif _puppet === null>>
<<if $B.phase == "move">>
<div style="display: flex; justify-content: center; align-items: center; height: -webkit-fill-available">
<<link "[MOVE]">>
<<set _m = $puppets.indexOf($B.subject)>>
<<set $puppets[_i] = $B.subject; $puppets[_m] = _puppet>>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<</link>>
</div>
<</if>>
<</if>>
}}}
We also want a way to move our characters around on the battle grid. That happens here, in the final branch of {{{actorlist puppets}}}. If the {{{phase}}} of the battle is "move", empty tiles will generate a link that moves the selected puppet to that tile, and replaced their existing tile with a null. By default, this doesn't take up an action; players can move around all they want during their turn.
There are also additional action functions that interact with the grid, {{{massAttack}}} and {{{pushAttack}}}. The former can target small portions of the grid (rows, columns, or a + shape), and the latter can forcibly move characters around the grid.
This is just one of many features you can use with the battle grid. You may want to have it interact with threat targeting by giving characters in higher rows more starting threat or have them gain more per attack. By default, characters in the back rows can still attack freely, but you could restrict them to ranged attacks, as is the case in <i>Final Fantasy II</i>. Other <i>Final Fantasies</i> take a softer approach, with back-row characters merely gaining a bonus to defense at the expense of melee attack damage. <i>Radiant Historia</i> uses push attacks as a core gameplay mechanic, with the ability to force multiple enemies into the same square and damage all of them with a single attack. Try experimenting!
<h2 id="missMechanics">Misses and Critical Hits</h2>
Once upon a time there was a wargame called <i>Chainmail</i>, which used random dice rolls as a method of combat resolution. This game served as the basis for the role-playing game <i>Dungeons & Dragons</i>, which inherited its dice-based mechanics. Seemingly every computer RPG is based, in some way, on <i>Dungeons & Dragons</i>, and they all unthinkingly copied its mechanics too, including, for some godforsaken reason, the random dice-roll mechanics.
In <i>Dungeons & Dragons</i>, to perform an attack, you must roll a 20-sided die. If the result of the roll compares favorably to the target's Armor Class stat, your attack hits! Otherwise, it misses. As a little bonus, if you roll a 20 on your attack, you can deal a critical hit, which can do a number of things depending on the system but usually involves a multiplier on your final damage result. This is typically counterbalanced by a roll of 1 equating to a "critical miss" or "critical failure" that automatically misses regardless of other modifiers and may do other nasty things to the attacker.
<i>Dungeons & Dragons</i>, in turn, formed the backbone of the first computer RPGs, which were copied by the next generation of computer RPGs and on and on until this mechanic became so entrenched in the genre that no one bothered to question it anymore. So now we get to see "miss" and "critical hit!" messages in our modern RPGs too, all because an old wargame wanted to add a little randomness to its mechanics. Here I'll teach you how to be a mindless <i>D&D</i> copycat too.
Disclaimer: I hate this mechanic with the intensity of a thousand burning suns. I find it infuriating in every RPG I have ever played and I do not believe it adds any depth to gameplay whatsoever, at least in this extreme form. When your player cannot reliably predict the results of their actions -- when you take control away from them -- it violates one of the fundamental tenets of game design, the principle of consistent action-and-response. Players can succeed or fail your challenges not as a consequence of their actions, but by factors completely outside of their control. I consider this a bad thing. But other people, apparently, disagree, so I have grudgingly implemented this feature so that I can faithfully claim my engine can do everything RPG Maker can. However, it is turned off by default and I implore you to keep it that way unless you have a very, very good reason to do otherwise.
For more on this topic and how to use randomness responsibility, see the video "<a href="https://www.youtube.com/watch?v=dwI5b-wRLic" target="_blank">The Two Types of Random in Game Design</a>" by Game Maker's Toolkit.
<h3 id="misses">Accuracy and Misses</h3>
If you look at "Damages and Formulas", you'll see this widget at the top:
{{{
<<widget "accuracyCheck">>
(...)
<<include "accuracy formula">>
<</widget>>
}}}
It's called at the start of {{{<<echoDamage>>}}}, after the protection check but before damage calculation or anything else.
The accuracy formula is offloaded to another passage so you can modify it without having to fuss with the code of the entire "Damage and Formulas" file. By default, I used a formula roughly based on <i>Pokemon</i>'s:
{{{
<<if $action.accuracy === true>>
<<set _hit = true>>
<<else>>
<<set _acc = $action.accuracy>>
<<if subject().stats.hasOwnProperty("Accuracy")>>
<<set _acc = Math.max(_acc + (subject().get("Accuracy")-100),setup.MIN_ACCURACY)>>
<</if>>
<<if _target.stats.hasOwnProperty("Evasion")>>
<<set _acc = Math.max(_acc - _target.get("Evasion"),setup.MIN_ACCURACY)>>
<</if>>
<<set _toHit = random(1,100)>>
<<if _toHit <= _acc>>
<<set _hit = true>>
<<else>>
<<set _hit = false>>
<</if>>
<</if>>
}}}
The first thing you should notice here is that accuracy is a property of the action itself. You can set a default value in StoryInit (by default this is {{{true}}}) for all actions if you don't want to define one for each action individually.
If the {{{accuracy}}} property contains the Boolean {{{true}}}, the accuracy check is completely bypassed. We mark {{{_hit}}} as {{{true}}} and that's all. This value is used for "always accurate" attacks that bypass the following check:
{{{
<<set _acc = $action.accuracy>>
<<if subject().stats.hasOwnProperty("Accuracy")>>
<<set _acc = Math.max(_acc + (subject().get("Accuracy")-100),setup.MIN_ACCURACY)>>
<</if>>
<<if _target.stats.hasOwnProperty("Evasion")>>
<<set _acc = Math.max(_acc - _target.get("Evasion"),setup.MIN_ACCURACY)>>
<</if>>
}}}
Though these stats do not exist in the default engine, I've included handling for "Accuracy" and "Evasion" modifiers. (Note that the {{{hasOwnProperty()}}} check ensures this will only be done if the correct stats exist, so don't worry about problems if you don't want to use those stats.) I use a simple adjustment formula based on percentile values: The attack gains accuracy equal to the attacker's accuracy stat offset by 100 (so if they had an accuracy stat of 120%, they would gain 20% accuracy; if they had a stat of 80%, they would lose 20% accuracy, etc.) and loses accuracy equal to the target's evasion stat. (In both cases, we do a common-sense check to bound the accuracy value against {{{MIN_ACCURACY}}}, to avoid weird things like negative accuracy values and in case you don't want it to be possible to reduce accuracy to 0% or something.)
Note that these modifers mean <b>an accuracy value of 100 does not guarantee a hit</b>. Accuracy penalties and evasion stats can reduce the attack's accuracy and cause it to still miss. This is why we need a different value to denote attacks that are truly always accurate.
{{{
<<set _toHit = random(1,100)>>
<<if _toHit <= _acc>>
<<set _hit = true>>
<<else>>
<<set _hit = false>>
<</if>>
}}}
We then do a simple percentile roll. If we roll under the adjusted accuracy value, the attack hits; otherwise, it misses. We record the result in the {{{_hit}}} variable. (Note that due to the boundary values picked for the {{{random()}}} call, an accuracy of 0 will always miss.)
{{{
<<if _hit === false>>
<<print setup.MISS_MESSAGE>>
<<elseif _hit === true>>
(proceed as normal)
}}}
It's then up to you what you want to do with a missed hit. By default, this code runs in {{{<<echoDamage>>}}}, printing the miss message and nothing else.
One thing you should note is that, by default, magical attacks (denoted by a {{{useSpecial}}} property of 1) cannot miss. This is consistent with most console RPGs (but not <i>Dungeons & Dragons</i>!). This is probably because making the player waste not only a turn but a limited resource was considered too much of a dick move even for old-school game designers, and they were right.
The reason I hate this mechanic is because I feel the variance in result is too high. A missed attack is a complete nullification of your action; it is possible to make no progress in the battle at all no matter how many actions you take, if you are very unlucky and keep missing. (And don't even get me started on enemies whose "gimmick" is having high evasion.) I can understand, in principle, there being some benefits to preventing the player from getting too complacent, but maybe be a little bit nicer about it? I played one RPG where missed attacks did half damage instead of none, the conceit being that the attack struck a glancing blow instead of missing completely. Try something like that and see how it plays. (You could even play with it further by providing equipment or other modifiers that change the proportion of graze damage. Perhaps you have a weapon that does low base damage but loses less damage on a miss, for a weaker but more reliable attack? Be inventive! Don't just mindlessly copy <i>Dungeons & Dragons</i> like every designer before you!)
<h3 id="crits">Critical Hits</h3>
Missed hits are sometimes "balanced" by their inverse, critical hits. Critical hits typically do greater damage than a normal hit, and may have other properties like inflicting an injury.
Like with accuracy, the formula is wrapped in a widget and offloaded to another passage for modularity purposes. My default critical formula is very similar to the accuracy formula:
{{{
<<set _critChance = $action.critRate>>
<<set _toCrit = random(1,100)>>
<<if subject().stats.hasOwnProperty("Skill")>>
<<set _critChance += subject().get("Skill")>>
<</if>>
<<if _toCrit <= _critChance>>
<<print setup.CRIT_MESSAGE+" ">>
<<set $dmg *= $action.critMultiplier>>
<</if>>
}}}
Like with accuracy, critical rate is an action property but has a default value you can set in StoryInit. (And just like how magic can't miss, by default it can't crit either.) If you've defined a stat called "Skill" for the attacker, its value is added to the action's percentile critical chance, and then we do a percentile roll against the result. (Most RPGs call the stat with this function "Luck", but if you haven't caught on by now, I really hate luck, so I call it "Skill" just out of spite.) If a hit is critical, its damage is multiplied by {{{critMultiplier}}}, another action property that has a default value (1.5 by default).
{{{
<<if !$B.phase && $dmg > 0>>
<<critCheck>>
<</if>>
}}}
Note that, because critical hits directly modify the damage value, this check is called in {{{<<damageCalc>>}}}, not {{{<<echoDamage>>}}}, which in turn is only called if the attack passes its accuracy check. (Note that passing the "nocalc" argument to {{{<<echoDamage>>}}} will also bypass the call to {{{<<damageCalc>>}}} and thus the critical hit check.) This check is placed at the very end of {{{<<damageCalc>>}}}, after all other factors have been calculated.
I've included some common-sense checks to gate the critical check. We only want criticals to be calculated when the attack is actually performed (in the action phase), not as part of the damage preview in the confirm phase. We track battle phase with the {{{phase}}} property of the battle object, and it is set to {{{null}}} during the action phase, which evaluates to a falsy value; thus, {{{!$B.phase}}} ensures critical checks can only occur during the action phase. I have also decided that if the damage is absorbed (less than 0) due to elemental factors, the attack can't be critical. You can change this if you wish.
You can do a lot more with critical hits than just a damage multiplier. <i>Pokemon</i> lets critical hits bypass stat modifiers; similarly, RPG Maker VX has critical hits ignore defense. Several jRPGs like <i>Final Fantasy</i> have their "Luck" stat increase not just your critical chance, but also your critical multiplier. Our old friend <i>Dungeons & Dragons</i> famously has several critical hit tables that involve rolling again to see what type of gruesome injury you inflict, which can incur additional penalties from stat debuffs to instant death.
If you do use critical hits, I implore you to consider this question: Do enemies get them too? You can run into problems if you do. Players usually have healing skills and enemies usually don't, so RPGs often have a health asymmetry. Enemies tend to have much more health than player characters, because they are not expected to heal, so their health has to last them the whole battle. Player characters, meanwhile, are expected to operate on a cycle of healing after taking hits, so they can usually only survive a comparatively small number of hits from the enemy. If you're too incautious with your critical mechanics, you can make your enemies' damage variance far too large, potentially oneshotting characters who would otherwise be able to survive several hits. This can, like excessive miss rates, edge the game into Roulette territory, making it too frustrating.
<h2 id="bestiaryDocs">Bestiary/Enemy Encyclopedia</h2>
RPGs are games that handle a lot of data. Players will want to know their enemies' statistics just as much as their characters', either to better formulate strategy or just out of curiosity. This is particularly helpful the more complex your battle system is and the more moving parts it has. Some information, like affinities for rare elements or chance-based item drops, may be opaque or non-obvious from just one encounter.
More and more RPGs provide this feature in some capacity: the GameBoy Advance remakes for <i>Final Fantasy</i> I-VI added a menu option for reviewing the statistics of every enemy encountered; third-party RPG Maker scripts provide a similar functionality; western RPGs usually feature a broad encyclopedia of gameplay and setting information that includes enemies; and <i>Pokemon</i>'s Pokedex is one of the most famous examples, where filling out the encyclopedia was actually the goal of the game. In light of this, I decided it was a feature worth adding in version 1.21 of this engine.
We need separate classes for both the bestiary itself and the individual entries. This is similar to the {{{Inventory}}} and {{{Item}}} classes (see the relevant section in [[Documentation|Documentation (Advanced)]]). These classes are {{{Bestiary}}} and {{{BestiaryEntry}}}. Both of them can be found within the {{{database-enemies.js}}} file, below the {{{Enemy}}} class definition.
<h3 id="bestiaryClass">Bestiary Class</h3>
{{{
window.Bestiary = class Bestiary extends Array
}}}
We want the ability to easily sort, manipulate, and iterate over the bestiary, so {{{Bestiary}}} is made a subclass of {{{Array}}}, a default object class that contains many useful functions such as {{{sort}}}.
{{{
for (let [name,enemy] of Object.entries(setup.enemyData)) {
if (typeof(enemy.bestiaryNo) == "number") {
this.push(new BestiaryEntry(name,enemy));
}
}
this.sort(function (a,b) { return a.bestiaryNo - b.bestiaryNo});
}}}
The constructor then automatically reads through the enemy database and adds a new entry for each one. This is done automatically, so you don't need any arguments to create a working {{{Bestiary}}} object, just a working enemy database. Once the bestiary is fully populated, it's sorted by {{{bestiaryNo}}}, short for "bestiary number", a variable defined in the enemy's database entry. This gives you control over what order enemies are displayed in your bestiary; for instance, you probably want enemies the player will encounter early to appear at the start of the bestiary, and you may wish to group similar enemies together, as with <i>Pokemon</i>'s evolution lines.
{{{
if (typeof(enemy.bestiaryNo) == "number")
}}}
Note this conditional. Some database entries, such as alternate form definitions, may not be intended as full entries, or you may just want to keep certain special enemies hidden from the player. You can do this either by giving them a nonnumber value {{{bestiaryNo}}}, or by not giving them one at all. Entries will only be added to the bestiary if the enemy has a valid {{{bestiaryNo}}}.
{{{Bestiary}}} also has a number of method functions, most of them very simple shortenings of {{{Array}}} functions. The {{{fetch}}} function allows us to quickly grab a bestiary entry by name without having to know its index value, and the {{{encountered}}} function returns an array of only the entries the player has encountered. {{{nextEntry}}} and {{{lastEntry}}} simply search over the bestiary and return the next encountered enemy; this is used for menu navigation.
<h3 id="bestiaryEntryClass">BestiaryEntry Class</h3>
The {{{BestiaryEntry}}} class contains all information about the enemy relevant to the bestiary.
{{{
this.name = name;
this.encountered = false;
this.defeated = 0;
this.statsKnown = {
"hp": false,
"gp": false,
"xp": false
};
this.tolerancesKnown = {};
}}}
Above is the main part of {{{BestiaryEntry}}}'s constructor. It has a {{{name}}} property matching the enemy's address in the enemy database, a Boolean flag for whether or not the enemy has been encountered by the player, a counter for how many enemies the player has defeated, and a list of the enemy's statistics. This list allows you to control how much information you want to reveal to the player and when; perhaps they only learn the enemy's statistics when they beat one, or when they use a scanning ability, or when they add it to their Pokedex. It may also enhance gameplay to not reveal an enemy's elemental affinities right off the bat, and instead force players to learn them by testing specific attacks on the enemy.
(Tolerances are listed in a separate object for reasons relating to their eventual front-end display.)
{{{
if (enemy.stats !== undefined) {
for (let stat in enemy.stats) {
this.statsKnown[stat] = false;
}
}
if (setup.ELEMENT_LIST !== undefined) {
setup.ELEMENT_LIST.forEach(function (elmn) {
this.statsKnown[elmn] = false;
},this);
this.elementData = this.access().elements;
}
if (enemy.tolerances !== undefined) {
for (let tolerance in enemy.tolerances) {
this.tolerancesKnown[tolerance] = false;
}
}
}}}
By default, {{{statsKnown}}} only contains markers for the enemy's hit points, monetary reward, and experience points, stats that every enemy is assumed to have. This section populates it with additional stats that may be more custom to your game: core stats, elemental affinities, and ailment tolerances.
{{{
if (enemy.alts !== undefined) {
this.altSkin = null;
this.altsKnown = {};
enemy.alts.forEach(function (alt) {
this.altsKnown[alt] = false;
},this);
}
}}}
Finally, we have a section for alternate forms: Versions of the same enemy that, while not warranting a completely separate entry, may still be significant enough to list. This may be different stages of a boss, such as Rose Quartz's three stages in <i>Cartoon Battle</i>, or the regional variants of Pokemon. If you want to include these, you will need to create an {{{alt}}} property in the enemy's database entry that looks like this:
{{{
"alts": ["Rose 2","Rose 3"]
}}}
That is to say, an array containing string names for each alt form, corresponding to other entries in the database. (This means that alt forms will also need database entries, but should <i>not</i> have {{{bestiaryNo}}}s, or else they will be made completely separate entries in the bestiary.)
{{{
get data () {
return (setup.enemyData[this.altSkin] || setup.enemyData[this.name]);
}
}}}
You may notice that {{{BestiaryEntry}}}'s getter for the {{{data}}} property is different than for other objects. This is to allow for easy access to alt form data. If the {{{BestiaryEntry}}} is wearing a valid {{{altSkin}}} property, the alt form's data is grabbed instead. Otherwise, we default to the standard entry.
{{{
get baseData () {
return setup.enemyData[this.name];
}
}}}
However, complicating this issue is that some alt forms exist but do not have valid entries for certain data, because I didn't have the bestiary in mind when I designed them. Oops! This workaround lets you bypass that and take data directly from the base form when necessary.
{{{
access () {
return new Enemy(this.name);
}
}}}
{{{BestiaryEntry}}} contains this method function, which may seem odd but is actually quite useful. {{{BestiaryEntry}}} contains a lot of data, but it doesn't contain all the functions and processing found in a real, live {{{Enemy}}} object. For complicated cases, we may need to grab the data from an {{{Enemy}}} object instead of the database entry; for example, if we need some processing performed in {{{Enemy}}}'s constructor or one of its getter functions. <b>Note that creating a whole new object is a computation-intensive process, so try not to overuse this function.</b>
{{{bestiaryEntry}}} also contains several getter functions for things like the enemy's database entry and in-game description. To get specific stats, however, we need something more complicated:
{{{
get (stat) {
if (this.statsKnown[stat] === true) {
let s = stat.toLowerCase();
if (s == 'hp' || s == 'gp' || s == 'xp') {
return (this.data[s] || this.baseData[s] || 0);
} else if (setup.ELEMENT_LIST.includes(s)) {
return this.elementData[s];
} else {
let statData = (this.data.stats || this.baseData.stats);
if (typeof(statData) == 'object') {
return (statData[stat] || this.baseData.stats[stat]);
} else {
console.log("ERROR in BestiaryEntry.get(): database entry has no stat object");
return;
}
}
} else {
return "???";
}
}
}}}
We need to pass the name of the stat we want to this function. If the stat isn't known to the player (the corresponding flag in {{{statsKnown}}} is not {{{true}}}), we should just return a generic "unknown" message, by default a simple string of three question marks. Otherwise, we will use an {{{if}}} statement to create branching functionality that allows us to find the correct stat in the enemy's database entry. HP, GP, and XP have single properties with unique names, making them straightforward to access; but because elemental affinities and core stats are stored in sub-containers, we need to cover them under separate branches.
{{{
else if (setup.ELEMENT_LIST.includes(s)) {
return this.elementData[s];
}}}
Elemental affinities are much more complicated that other stats, and we need the processing of a live {{{Enemy}}} object to get everything we need from them. Fortunately, this was handled in the constructor already, storing the post-processed data in this {{{elementData}}} property.
{{{
let statData = (this.data.stats || this.baseData.stats);
if (typeof(statData) == 'object') {
return (statData[stat] || this.baseData.stats[stat]);
} else {
console.log("ERROR in BestiaryEntry.get(): database entry has no stat object");
return;
}
}}}
With core stats, we have to be careful: Because it's possible for a database entry to have no {{{stats}}} property (if they are an alt form, for instance), we need to grab that first to make sure it's in the format we want -- in this case, a generic object. If it is, all is well, but if not, we need to log an error.
(Note the dual use of {{{data}}} and {{{baseData}}} here. We check {{{data}}} first, but if that turns up an {{{undefined}}}, we can fall back on {{{baseData}}}, which should always have a fully-populated database entry.)
<h2 id="crisis">Crisis/Limit Breaks</h2>
<<nobr>>
<center>
<figure>
<img @src="setup.ImagePath+'documentation/LBexample.PNG'" />
<figcaption>
<span style="font-size:10pt">(Screenshot from <i>Final Fantasy VII</i>, from the Final Fantasy Wiki)</span>
</figcaption>
</figure>
</center><</nobr>>
The term "Limit Break" was an idea popularized by the video game <i>Final Fantasy VII</i>. In that game, characters filled up a "limit" guage as they took damage, and when it was full, they could unleash a powerful special attack. Later <i>Final Fantasy</i> games would continue to iterate on the concept, most notably the Overdrive mechanic of <i>Final Fantasy X</i>, which also allowed characters to fill up the guage through other methods.
This is a popular mechanic in RPGs, largely because it provides an opportunity to see cool, flashy, cinematic moves that make the player feel awesome and powerful. For that reason, I chose to add support for the feature in version 1.23, calling it "Crisis" as an homage to <i>Last Scenario</i>.
Limit breaks create an interesting paradox: You get stronger the closer you are to losing. This can help modulate the difficulty of your battles by allowing players to bounce back if they're decimated by an enemy attack. You do have to be careful with this, though: if it's too easy to charge up limit breaks, players will be able to trivialize a lot of fights even if they aren't at the intended level of strength or skill. It may even unintentionally incentivize players to be slightly weaker than the intended benchmarks so they can pull off more limit breaks. <i>Final Fantasy VII</i> itself addressed this problem by resetting the limit guage when a character was KO'd, ensuring that characters had to be tough enough to at least survive the enemy's attacks; however, many other games leave limit progress intact on KO.
An important variable to define is what proportion of HP characters need to lose before they can pull off a limit break. Most <i>Final Fantasy</i> games actually require characters to take significantly more than their maximum HP in damage, requiring the use of healing abilities for a successful charge. For example, in <i>Final Fantasy X</i>, a character taking their maximum HP in damage will only charge their limit guage by 30%. This is a good principle if you plan to emphasize teamwork and synergy in your battle system, as it requires characters to work together and support each other in order to use the system to its fullest. This can also be a good way to modulate the frequency of limit breaks in the first place.
(Of course, if you plan to have no or limited healing, this won't be possible. This is why the engine's default crisis modulator is extremely generous, only requiring characters to take 70% of their maximum HP in damage. Since there's no healing, this means characters can only use their crisis ability once; but in a system with healing, the same crisis modulator may allow them to pull off crisis abilities near-constantly.)
However, you need not constrain yourself to only thinking about limit breaks in terms of desperation attacks. You can let the player charge the meter through any behavior you wish, and this is in fact a great way to incentivize players to play the way you want them to. <i>Final Fantasy X</i> notably gave characters multiple methods of charging, with each being unlocked over the course of the game after the character had performed a number of actions related to the method. In this way, players were given more control over how to customize their characters: they could let a strong warrior character charge limit by inflicting damage or killing enemies, or turn dangerous situations into opportunities with modes that charged limit when characters were alone or in critical health.
<i>Radiant Historia</i> provides a more focused example: The characters' equivalent of a limit break guage charges when characters combo long strings of attacks together. This combo mechanic is a core feature of <i>Radiant Historia</i>'s battle system and the game becomes much more fun once you understand it, but most jRPG players may not give it much thought; by tying it to the cool, flashy limit break attacks, it draws the players' attention and encourages them to master it.
You can even introduce an interesting resource tradeoff by making limit break progress useful for other abilities as well. In <i>Alter A.I.L.A. Genesis</i>, characters gain progress towards a powerful, ultimate attack every time they use a basic ability, but these progress points can themselves be used for other special abilities. This adds an interesting new dimension to battles, as you must make tradeoffs between using a moderately powerful ability immediately or waiting to build up to a stronger one.
This ties into one final point: An interesting quirk of the original limit break system is that it cost no Magic Points or any other type of limited resource, unlike other strong abilities like spells. This made limit breaks even more powerful and enticing than they already were. This makes a degree of sense, since they are still limited by the limit guage itself, but it also ran the risk of making them overpowered. Tip the balance too far, and limit breaks can end up reducing tactical depth by turning the optimal strategy into just waiting to use them over and over again. (This was, in fact, a common criticism of <i>Alter A.I.L.A. Genesis</i>.) You need not be limited by this mold; you can make limit breaks cost energy, MP, or some other resource to constrict their use and force the player to think about them more carefully.
Like everything else, if you choose to use limit breaks in your game, use them with thought and care, not just because they seem cool or because other games did it first.
<h3 id="crisis.code">Implementation</h3>
The default engine settings support a straightforward, <i>Final Fantasy</i> style of limit breaks. First, we have to give the characters these actions in the first place:
{{{
class Puppet extends Actor{
constructor(name){
(...)
this.crisis = [];
if (this.data.crisis instanceof Array) {
for (let n of this.data.crisis) {
this.crisis.push(new Action(n));
}
} else if (typeof(this.data.crisis) == "string") {
this.crisis.push(new Action(this.data.crisis));
}
}}}
In the Puppet constructor, we create an array property called {{{crisis}}} and fill it with Crisis actions, just like we do for regular actions. You can give characters either a single Crisis action or a list by adding it to their database entry. (Crisis actions themselves are created just like any other action, their database entries contain the "crisis" property.)
{{{
[Q] = basic action
<<if subject().lastAction !== null>> | [W] = last action (<<print subject().lastAction.name>>)<</if>>
<<if subject().crisis instanceof Array && subject().crisis.length > 0>> |
<<if subject().crisisPoints >= 100>>
<<set _style = "font-weight:bold">>
<<else>>
<<set _style = "color:gray">>
<</if>>
<span @style=_style> [E] = <<crisisLink>></span>
<</if>>
}}}
Then, on the action list screen, we add this segment after the standard hotkey notifications (for basic and last actions) to allow players to access their characters' Crisis abilities.
{{{
<<if subject().crisis instanceof Array && subject().crisis.length > 0>>
}}}
This clause ensures the Crisis toggle will only appear if the character has Crisis actions in the first place. If their {{{crisis}}} property doesn't exist or is unpopulated, nothing will display here. (This means that if you don't want to have Crisis abilities in your game, you can just not set them and the player will never know -- puppets will still have the {{{crisis}}} property, but it will be empty and thus fail this check.)
{{{
<<if subject().crisisPoints >= 100>>
<<set _style = "font-weight:bold">>
<<else>>
<<set _style = "color:gray">>
<</if>>
<span @style=_style> [E] = <<crisisLink>></span>
}}}
We then add a little bit of style to the notification itself. If the player can use a Crisis (their {{{crisisPoints}}} are at 100%), we'll bold the link to draw their attention; if not, we'll gray it out so it's less obtrusive.
{{{
<<widget "crisisLink">>
<<if typeof($args[0]) == "string">>
<<set _text = $args[0]>>
<<else>>
<<set _text = "Crisis">>
<</if>>
<span id="crisisLink">
<<link _text>>
<<replace "#phase">><<include "crisis actions">><</replace>>
<</link>>
</span>
<</widget>>
}}}
The {{{<<crisisLink>>}}} widget itself is straightforward: It's just a link that displays the puppet's crisis actions when clicked. If you want to use a different term than "Crisis", you can also pass alternate text to the widget and that will be displayed instead.
{{{
<<if $args.includes("crisis")>>
<<set _actions = _char.crisis>>
}}}
The "crisis actions" passage is functionally identical to the standard "actions" passage, the only difference being that it iterates over Crisis actions rather than regular actions. That's accomplished through this little handler in {{{<<actionlist>>}}}.
From here, the Crisis action can be handled like any other and reuses much of the same code, though we have to add some additional checks to account for the new resource:
{{{
window.actionStandardCheck = function actionStandardCheck (action) {
return (...) (action.crisis && subject().crisisPoints < 100);
}
[action effects]
<<if $action.crisis && def subject().crisisPoints>>
<<set subject().crisisPoints = 0>>
<</if>>
}}}
The selection link is disabled if the character's Crisis points aren't at 100%, and Crisis points are reset to 0 after use.
To give characters Crisis points, we add this passage to the end of {{{<<echoDamage>>}}}:
{{{
<<if Number.isInteger(_target.crisisPoints) && $dmg > 0>>
<<switch _target.crisisMode>>
<<default>>
<<set _target.crisisPoints += Math.round(($dmg/_target.maxhp)*100*_target.crisisFactor)>>
<</switch>>
<</if>>
}}}
The default formula is very simple, based on <i>Final Fantasy X</i>'s formula for Stoic mode: Crisis points gained are equal to the proportion of max HP taken in damage multipled by a modulator variable. The default Crisis factor is 10/7, meaning characters will fill their Crisis points when they have taken 70% of their max HP in damage, but you can specify unique values for different characters.
You can also see functionality here for alternate Crisis modes a la <i>Final Fantasy X</i>, but no others are implemented at this time.
<h3 id="crisis.stash">Stash</h3>
Rogue's "Stash" Crisis ability is particularly complicated, so I will cover it here.
{{{
"Stash": {
"crisis": true,
"phase": "Stash Phase",
"info": `Use an item for free.`,
"desc": `Rogue may have kept a few of the party's items tucked away for themselves. But now that the chips are down, they've got to use everything at their disposal!`,
"preview": null
}
}}}
If you look at Stash's database entry (shown here), you'll find it's very minimal. This is because, unlike other actions, Stash is never meant to be directly used. Its only purpose is to provide the player something to click on, at which point they will be forwarded to a submenu to choose the actual action they will use that turn.
The Stash Phase can be viewed in {{{Battle Phases.tw}}}, but it is essentially identical to the action list menu. The key lies in an additional attribute we add to Rogue during character generation:
{{{
"specialInit": function (actor) {
(...)
actor.stash = [
new ItemAction("Panacea"),
new ItemAction("Bottled Chi"),
new ItemAction("Adrenaline"),
new ItemAction("Stoneskin"),
new ItemAction("Nootropic"),
new ItemAction("Stimulant"),
new ItemAction("Powdered Glass"),
new ItemAction("Grenade"),
new ItemAction("Flamethrower"),
new ItemAction("Gas Bomb"),
new ItemAction("Flashbang"),
new ItemAction("Calamity Bomb")
];
actor.stash.forEach(function (item) {
item.free = true;
item._crisis = true;
});
}
}}}
The {{{stash}}} array is a list of the actions the player can choose from in the Stash Phase. If the character doesn't have a {{{stash}}} attribute, you'll get an error message and the code will shut down before it starts. Since we're only interested in the action and not the items themselves, we can just use {{{ItemAction}}}s here.
Note that the stash list has no correlation to the player's actual inventory, and in fact I cut out all the minor status cures that would be redundant with the Panacea. This gives you more control over the stash list, perhaps allowing the player to unlock additional items as they level up.
{{{
<<if subject() instanceof Puppet && $action instanceof ItemAction && !$action.free>>
<<print $inventory.decItem($action.name)>>
<</if>>
}}}
However, this runs into a little problem, here in the action phase. If the action was an {{{ItemAction}}}, we're supposed to remove the corresponding item from the player's inventory -- but the whole point of Stash is that it's not supposed to do that!
{{{
actor.stash.forEach(function (item) {
item.free = true;
item._crisis = true;
});
}}}
That is the purpose of this additional code. It runs through the {{{stash}}} array and modifies every action to have the {{{free}}} attribute, which will bypass the item decrement check. We also need to make them behave as Crisis abilities by flagging their {{{_crisis}}} attribute -- otherwise, they wouldn't reset the user's Crisis points!
These are the additional factors you'll need to add in order for Stash to work. The principles you learned here can also be generalized to other types of submenu abilities.<<set $scenario = "gf1">>
<<include "Preparation">>
<<set $B.subject = $puppets[0]>>
<<set $B.subject.en = 10>>
<<set $B.target = $enemies[1]>>
<<if $subtest.includes("dark")>>
<<set $B.style = "dark">>
<<addclass "html" "dark">>
<</if>>
<<if $subtest == "Antidote" || $subtest == "Grenade">>
<<set _action = new ItemAction($subtest)>>
<<run $puppets[0].addEffect("Poisoned",{power: 1, weight: 1, noPopup: true})>>
<<set $B.target = $puppets[0]>>
<<elseif $subtest == "block">>
<<set $enemies[1].addEffect("Bubble",{power: 1, noPopup: true})>>
<<set _action = new Action("Flurry")>>
<<elseif $subtest == "Thorns">>
<<run $enemies[1].addEffect("Thorns",{power: 1, noPopup: true})>>
<<set _action = new Action("Sword")>>
<<elseif $subtest.includes("regen")>>
<<set subject().HPregenFlat = 5; target().HPregenFlat = -5>>
<<set _action = new Action("Knife")>>
<<else>>
<<set _action = new Action($subtest)>>
<</if>>
<<actionLink _action>>
<<goto "Anim Test Live">><<if $subtest.includes("regen")>>
<<endOfRound $enemies>>
<<newTurn $puppets>>
<<goto "Battle!">>
<<else>>
<<include "action phase">>
<</if>>Version 2.01
<ul>
<li>New action properties: {{{enemyCD}}} and {{{nameCD}}}. These values will be automatically read to set the values of an enemy's cooldown Map when actions are executed. They default to the action's {{{cooldown}}} and {{{name}}} properties, but they can set to custom values if desired.</li>
<li>Continuing in the theme of "features I implemented embarassingly late", player actions will now decrement cooldown at the start of a round.</li>
</ul>
Version 2.00.1
<ul>
<li>HP cost will now be automatically deducted when using HP-consuming skills.</li>
</ul>
Version 2.00
<ul>
<li>Added a new file in <code>javascript/custom</code>: <code style="font-weight:bold">mods.js</code>. This file is designed as a catch-all for any miscellaneous code the user wants to add to the JavaScript. It consists of a function, <code>customMods</code>, which is run at the end of StoryInit. (This placement is to ensure any relevant <code>setup</code> variables are defined first.) Currently, the code for Puppets' Energy- and Crisis-related attributes have been moved there, along with <code>XPtoNext</code> and <code>initialThreat</code>. This way, you can easily modify the specific functions you need without needing to edit the class definition.</li>
<li>The <code>addeffect</code> widget has been depreciated, and its code has been moved to <code>Actor.addEffect</code>.</li>
<ul><li>"effect adder custom blocks" has been depreciated, and its functionality is now handled through properties of Effect objects: <code>block</code>, <code>blockCondition</code>, and <code>blockText</code>. Block effects now have a <code>priority</code> attribute as well to determine the order in which they resolve.</li>
<li><code>applyEffect</code> has been completely redone to better mesh with this change. It is now much simpler and is only capable of applying unmodified effects to a single target. For more complicated behavior, just use <code>addEffect</code> directly.</li>
<li>Actions have been edited to comply with the above change. Let me know if any bugs occur.</li></ul>
<li><code>Puppet.getStatCost</code> has been depreciated.</li>
<li>Indirect damage will no longer trigger shield effects.</li>
<li>EN cost is now deducted before actions execute, rather than after. This also allows Focus to work as intended without any additional code.</li>
<li>Effect duration is now capped at a number specified by <code>DURATION_MAX</code>, set as part of <code>setup</code>.</li>
<li>Shield effects should now block status ailments. Additionally, if a damaging attack is blocked or dodged, status effects inflicted by the attack will also be blocked.</li>
<li>Future applications of the same effect will now add to the effect's duration instead of resetting it.</li>
<li>Fixed a glitch in Thorns (and any similar effects) where the original target would still take damage. Shield effects that inflict damage on someone else should no longer have data bleed leading to unusual behavior.</li>
</ul>
Version 1.27
<ul>
<li><code>deathMessage</code> can now be defined as a database property.</li>
<li>Equipment slots can now be defined in <code>StoryInit</code>, through the variable <code>DEFAULT_EQUIP_SLOTS</code>, or as a database property, instead of being hardcoded in the Actor constructor.</li>
<li>Added a new support function, <code>resetAnimation</code>, that can be used to reset an animation mid-play. This function has been used to tweak hit animations such that shaking popups now take the same amount of time as non-shaking popups, hopefully reducing the time spent waiting for long multi-hit attacks to resolve.</li>
<li>The delay between popups can now be adjusted through the <code>ANIM_DELAY</code> variable.</li>
<li>Removed the "effect adder synonyms" passage. Functionality is now handled through a <code>synonym</code> property for Effects. If you define a <code>synonym</code>, the effect will be synced to that tolerance during tolerance calculation.</li>
<li><code>uncontrollable</code>, <code>untargetable</code>, and <code>shield</code> effects now operate the same way as <code>hold</code> effects, preventing unexpected overwrites.</li>
<li>Actors' <code>addEffect</code> and <code>removeEffect</code> functions have been compartmentalized into a separate file, <code>effect-manager.js</code>, for easier customization of effect logic.</li>
<li>Animations can now be toggled on and off through the settings menu.</li>
<li>Link color preferences should now work correctly again.</li>
<li>Knocked Down puppets should be unable to act again.</li>
<li>Damage-over-time messages will no longer display if the victim is defeated.</li>
<li>Counterattacks and mark attacks should now animate correctly. They are also now handled on their own passage, rather than appended to the previous action.</li>
<li>"custom end of action effects" now runs as part of "action effects". <code>markAttack</code> and <code>hunterCheck</code> have also been moved to "custom end of action effects".</li>
<li>Counter logic has been offloaded to its own passage for modularity purposes.</li>
</ul>
Version 1.26.1
<ul>
<li>Health meters will no longer break if the player refreshes during a battle. Refreshing can still cause other problems, though, so try not to do it.</li>
<li>Removed the "enemy holds" passage and replaced it with a <code>priority</code> property for effects. Effects are now sorted by <code>priority</code> to determine the correct hold effect to resolve.</li>
<li>Fixed a bug where, in the case that a character was under multiple hold effects, removing just one effect would remove their <code>noact</code> property and allow them to act again. This has been corrected by defining a new property for effects, <code>hold</code>, and defining a getter function for <code>noact</code> in <code>Actor</code> that returns whether or not the character's effects contain a hold effect.</li>
<li>Gaining Alert from losing Stunned will no longer generate a popup.</li>
<li>Enemies will no longer generate a popup when righting themselves from Knocked Down.</li>
<li>Puppets will no longer generate a popup by curing their ailments via Struggle or Rest.</li>
<li>Puppets will no longer generate a popup at the start of battle for any ailments removed at the end of the previous battle.</li>
<li>Protector and damage-over-time effects should work correctly again.</li>
<li>HP regeneration will now be communicated via popups at the start of every round.</li>
</ul>
Version 1.26
<ul>
<li>Documentation has now been split into "Basic" and "Advanced" sections. The "Basic" section should hopefully help people with less coding experience get into the engine more easily.</li>
<li>Top-of-round effects should no longer trigger multiple times if an enemy uses an instant action as their first action.</li>
<li>Indirect damage will no longer create popup messages.</li>
<li>Puppets will no longer gain EN after every enemy turn.</li>
<li><code>actorBox</code> is now in its own file, "Widgets (Actor Box)", and each of its elements has been made modular.</li>
<li>Tightened restrictions on <code>uses</code>, <code>cooldown</code>, and <code>warmup</code> attributes in the <code>Action</code> constructor; they will now only be applied if they are integers.</li>
<li>You can now specify an <code>action</code> property for entries in the item database if you wish for their associated action to be different than their name.</li>
<li>Removed <code>DoT</code> and <code>Protector</code> subclasses, as they were unnecessary. Their special attributes are now handled by an additional catchall argument to the <code>Effect</code> constructor.</li>
<li>Removed the "lastaction mods" passage and replaced it with the "saveMod" property for actions. If you want to save a different action, assign a name to its {{{saveMod}}} property.</li>
<li>Added documentation for animation code.</li>
<li>Revised the installation instructions for clarity.</li>
<li>Fixed dummy links in the documentation headers and restored the external link icons for external links.</li>
<li>Reduced the line height of text in the documentation passages for easier reading and less dead space.</li>
</ul>
Version 1.25
<ul>
<li><b>NEW FEATURE:</b> Battle animations! Nothing fancy, but actor boxes will now shake and produce damage value popups during action phases. You can turn them off by disabling the <code>ANIMATIONS</code> flag in <code>StoryInit</code>.</li>
<ul><li>As a consequence of the above, enemy actions now occur one at a time, instead of all at once. Tell me if this produces any bugs for you.</li></ul>
<li><code>damageCalc</code> will now only attempt to append element messages if the relevant setup variable exists.</li>
<li>Fixed a bug in the number hotkey code.</li>
<li>In-battle actor display is now standardized through a widget, <code>actorBox</code>. This should make modifying the default display easier.</li>
<ul><li>The documentation has been updated to document this feature.</li></ul>
<li>Actor boxes for dead characters are now aligned to the top of the flex container, and their height is restricted to only the height of the character's name.</li>
<li>Dead characters now display a † (dagger) symbol in place of the status button.</li>
<li>Fixed a bug that prevented <code>oncePerTurn</code> actions from deactivating correctly.</li>
<li>Fixed a bug that would allow illegal actions to be performed through the last action button under some circumstances.</li>
</ul>
Version 1.24.1
<ul>
<li><code>newturn</code> and <code>endofround</code> have been renamed to <code>newTurn</code> and <code>endOfRound</code> for clarity.</li>
<li>Effect decay messages are now lumped into one box per character.</li>
<li>Effect decay messages have now been standardized into one widget, <code>decayMessage</code>, which is called in both <code>newTurn</code> and <code>endOfRound</code>.</li>
<li><code>newTurn</code> has been modified to also work with enemy parties, and is now called in the enemy phase.</li>
<li>Console asserts have been added to <code>newTurn</code> and <code>endOfRound</code> to enforce proper behavior.</li>
<li>Prone characters will no longer count towards the automatic turn end counter. This is to prevent a glitch where if all puppets were knocked down, the player would never get an opportunity to right them.</li>
<li>Actions that cost 0 EN will now display their cost again. Crisis actions will only display a cost if it is greater than 0.</li>
<li>Updated documentation to reflect changes.</li>
</ul>
Version 1.24
<ul>
<li>Rogue now has a special Crisis that allows them to use any battle item for free. Try it out and see if it breaks anything.</li>
<li>Tweaked Crisis display code so that it will no longer display if no Crisis abilities are specified in the puppet's database entry.</li>
<li>You can now set specific Crisis factors for individual puppets. They will still default to <code>CRISIS_FACTOR</code> if unset.</li>
<ul><li>Rogue now has a larger Crisis factor than other puppets: they will max their Crisis guage after losing 40% of their health.</li></ul>
<li><code>passagejump</code> actions will now forward the player to the passage specified by the <code>passagejump</code> attribute, consistent with the documentation for the <code>passagejump</code> getter.</li>
<li>Created an <code>actionCheck</code> function that condenses all the action checks into one call.</li>
<li>The last action notifier will now be greyed out if the action is unusable.</li>
<li>Fixed a bug related to the W hotkey.</li>
<li>Added discussion and documentation for the Crisis feature.</li>
</ul>
Version 1.23.2
<ul>
<li>Realized widgets couldn't be clobbered, so every widget is now in its own passage. If you wish to replace a widget with a custom version, just make a new passage with the same name in the <code>passages-unique</code> folder and it'll be replaced.</li>
<li>Database and class definitions have been partitioned into different files and folders for easier updating. You can now update the core JavaScript files without overwriting your custom databases.</li>
<li><code>statInfo</code> and <code>hiddenStats</code> definitions moved to StoryInit.</li>
<li>Fixed a bug that was causing <code>actionLink</code> to fail in certain circumstances.</li>
<li>The Q hotkey should no longer trigger the "end turn" button.</li>
</ul>
Version 1.23.1
<ul>
<li>Fixed a typo that was causing the effect adder to fail.</li>
</ul>
Version 1.23
<ul>
<li><b>NEW FEATURE:</b> "limit break" abilities! Puppets will fill up a Crisis bar as they take damage, and can unleash a powerful ability when it's full.</li>
<li>Prettified end-of-round messages. They now display in the style of actions instead of plain text.</li>
<li>Added handlers for indirect damage (e.g. damage-over-time) to <code>echoDamage</code>. Indirect damage will ignore most checks such as damage reflection, off-balance, and counters.</li>
<li>Damage-over-time damage is now handled through <code>echoDamage</code> rather than manually.</li>
<li>The <code>decay</code> function for Effects will now always return something, and will return an empty string if the effect didn't expire.</li>
<li>You can now pass a puppet as an argument to <code>actionlist</code>.</li>
<li>The "struggle" and "rest" commands will now correctly remove their respective statuses.</li>
<li>Fixed a bug in the "massAttack" actions and previews.</li>
<li>Hunter and counter checks are now hardcoded into the main battle passages. The widgets governing Mark and Hunter attacks have been offloaded to their own passage, "Widgets (Special Attacks)" for easier modularity.</li>
<li>Piercing attacks will now use the lower of the target's Defense and the stat minimum, instead of always using the latter. This allows them to gain the benefit from negative Defense.</li>
<li>Special deaths should no longer be skipped by the auto-endturn function.</li>
<li><code>B.specialmsg</code> has been changed to a temporary variable. Now you will no longer need to reset it when your special message has finished.</li>
<li>Cleaned up action display code. The various tests have been condensed into functions, found in the support functions file.</li>
<li>The clunky check for whether Sacrifice was used on the current turn has now been turned into a general feature: actions with the <code>oncePerTurn</code> property will gain a <code>used</code> property after use and cannot be used again until it is cleared, which occurs on <code>newturn</code>.</li>
<li>The clunky check for Sacrament and Blaspheny's prior element requirement has been turned into a general function, <code>actionElementCheck</code> in support functions.</li>
<li>Energy regeneration is now a property of puppets rather than a system constant. This allows you to make puppets with different regeneration values.</li>
<li>Actions that cost 0 EN will no longer display a cost value in the actions menu.</li>
<li>The compressed action info box will now direct the player to hover over actions when nothing is selected.</li>
<li>There is now an option to remove ULTIMATESTICKY effects within battle. Simply assign a value of "ultimate" to the <code>unsticky</code> argument.</li>
<li>Fixed a rare bug where rounding errors could cause damage to erroneously be considered absorbed.</li>
<li>Removed outdated reference images from the documentation.</li>
<li>Self-inflicted status ailments should now bypass Chi Shield, as intended.</li>
</ul>
Version 1.22.1
<ul>
<li>Fixed a bestiary-related bug that would occur when an enemy was struck with an element or ailment.</li>
<li>Fixed a bug in Sacrament and Blasphemy.</li>
</ul>
Version 1.22
<ul>
<li><b>NEW FEATURE:</b> You can now give characters an <code>onHit</code> function that will execute whenever they are struck by damage. Note that this is separate from damage reflection.</li>
<li>Several action functions have been restructured to use a single <code>args</code> argument, the properties of which make up the rest of the arguments. This will make it much easier to contruct action functions, as you can omit irrelevant arguments entirely and clearly label every argument you need. However, I may not have converted all existing actions to the new format correctly, so tell me if you encounter any bugs.</li>
<li><code>chiCheck</code> has been renamed to <code>effectCheck</code>, and can now check for any effect.</li>
<li>Fixed a bug with the "any" modifier in <code>randomTarget</code> and added support for enemies being affected by loss-of-control effects as well.</li>
<li>The code for the Bubble and Thorns effects have been generalized to work with other effects. The code now checks if the target of an attack has a "shielded" flag, then searches their effects for an effect with a "shield" flag and runs its "onHit" function.</li>
<li>Fixed a bug in <code>splashDamage</code> that created an extra blank line.</li>
</ul>
Version 1.21.3
<ul>
<li>Rearranged the conditional checks in <code>callEncounter</code> so that the bestiary update loop is only run if the bestiary is defined.</li>
<li>There is now a preview function for healing actions.</li>
<li>Items now have a getter for <code>special</code>.</li>
<li>The enemy status display should no longer throw error messages.</li>
<li>Enemy names should now display properly during the targeting phase.</li>
<li>Fixed a bug in <code>randomTarget</code> that caused problems when a party array was passed as an argument. It is now passed as a string instead.</li>
</ul>
Version 1.21.2
<ul>
<li>Added a section on the design of story-based/fixed progression.</li>
<li>Added a section on the design of skill progression.</li>
<li>Slightly edited the design sections to provide some more information and resources.</li>
<li>Added a return-home link at the bottom of info pages.</li>
</ul>
Version 1.21.1
<ul>
<li>The bestiary now flags every enemy as encountered (allowing them to appear in the bestiary) on game load.</li>
<li><code>gp</code> and <code>xp</code> properties are now in lowercase, for standardization with <code>hp</code>.</li>
<li>The code for formation and bestiary menu entries have been offloaded to separate twee files.</li>
<li>The <code>encountered</code> flag for bestiary entries is now flipped in <code>callEncounter</code> instead of the <code>Enemy</code> constructor.</li>
<li>Added documentation for the bestiary in Additional Features.</li>
<li>Fixed a glitch that prevented bestiary objects from reviving correctly.</li>
</ul>
Version 1.21
<ul>
<li><b>NEW FEATURE:</b> Bestiary/enemy encyclopedia. The code for its construction can be found in <code>database-enemies.js</code>. The bestiary provides highly modular function, with tracking for masking/revealing every single stat, if desired. You can observe it for yourself in a new menu option.</li>
<li>Further compartmentalized the actor display code.</li>
<li>The getter for enemies' <code>fullname</code> attribute will now default to their regular name. This has allowed for some streamlining of the battle display.</li>
</ul>
Version 1.20
<ul>
<li>Added a sticky item for you to test yourself. Start a new game and you'll find three mysterious rings in your inventory. Try them on and see what happens!</li>
<li>Also added a decurse station to let you remove sticky equipment. Pay the cleric 1 GP to appraise an unknown item or to remove a cursed item you've foolishly stuck on yourself.</li>
<li>Fiddled with a few <code>Item</code> properties:
<ul><li><code>info</code> and <code>desc</code> can now be dynamically modified based on the Item's properties just like with Actions.</li>
<li>You can now give Items a <code>fakeName</code> property, which will display in place of its real name until the item's <code>known</code> attribute is flipped to <code>true</code>. This is handy for hiding cursed items from the player.</li>
<li>To facilitate the above, Items' database pointer is now a separate property, <code>id</code>, from its display name. Use <code>id</code> for any code operations that require drawing from the database, and <code>name</code> for any time the item's name is being displayed to the player.</li>
<li>Renamed the <code>cost</code> property to "value" to avoid confusion with Actions' <code>cost</code> property. <b>Any custom code made in previous versions will need to be updated accordingly.</b></li></ul></li>
<li>You can now unequip items from the equipment screen without selecting a specific character again.</li>
<li>Sticky items can no longer be removed by replacing them with equipment from the same slot.</li>
<li>As part of the above, sticky equipment can no longer be removed with the <code>unequip</code> function by default. Pass an <code>unsticky</code> property to the <code>mods</code> argument to do so.</li>
<li>The equipment and inventory lists now have their own scrollbars, so you won't lose track of your selection when you're scrolling through long lists anymore.</li>
<li>The item shop (and the new decurse station) now use <code>live</code> and <code>update</code> to display your item points, for neatness.</li>
<li>Cleaned up some of the HTML in the menu screens. Several elements that were hardcoded are now defined as CSS classes, and the display code for actor blocks has been condensed into one widget with modular functionality. <b>This required changing some widget names, so custom menu code made in previous versions may be incompatible.</b></li>
<li><code>setInv</code> is now called in <code>user storyinit</code>. A blank inventory is still automatically initialized in StoryInit.</li>
<li>Fixed a bug in the inventory screen that stopped hotkeys from working if an item was selected after using or equipping another item.</li>
</ul>
Version 1.19
<ul>
<li>Accessibility features added: You can now change the default font to OpenDyslexic for easier reading with dyslexia, and change the link color if you have a condition that makes it difficult to pick out blue. <b>If you have one of these conditions, please tell me if these options are helpful and suggest changes if they are not.</b></li>
<li>Tweaked the link mouseover color to be a bit less bright.</li>
<li>Finally fixed the weird background color glitching when selecting input fields. It should now display black text on a white background, like everything else in the bleached style.</li>
<li>Changed the input field for spell costs to <code>numberbox</code> for ease of use.</li>
</ul>
Version 1.18
<ul>
<li><b>NEW FEATURE:</b> Equipment locking. If you set the <code>lockEquipment</code> attribute on a Puppet, the player will be unable to change their equipment. Note that you can still alter their equipment through manual <code>equip</code> and <code>unequip</code> executions.</li>
<ul><li>Specific equipment can also be locked. If its <code>sticky</code> attribute is truthy, you can put it on, but you can't take it off! Perfect for cursed items and the like.</li></ul>
<li>The equip-from-inventory screen is now neater, with characters who can't equip the selected item not appearing at all.</li>
<li>Color of Defeat is now restricted to Artist, purely for ease of testing the restriction mechanic.</li>
<li>It should no longer be possible to bypass an equipment restriction by equipping the item from the inventory screen.</li>
<li>Character names should no longer disappear when listed in the inventory screen.</li>
<li><code>checkRestriction</code> has been reversed, and is now called on an Actor with an Item as an argument. The Item-centric version still exists, but will be depreciated.</li>
<li>You can now hide specific core stats, preventing them from being displayed in the menu or status pane. To do so, add the stat name to the <code>setup.hiddenStats</code> variable in <code>stat class.js</code>.</li>
<li>Renamed the <code>inv</code> variable to <code>inventory</code> for clarity. The shorthand <code>inv()</code> still works.</li>
<li>Moved the definition for <code>V</code> to support functions.</li>
<li>Reorganized the code for the party menu to be a bit easier to use. Stat display code is now standardized to one template passage, and <code>equipmentlist</code> is now a passage rather than a widget.</li>
</ul>
Version 1.17
<ul>
<li><b>NEW FEATURE:</b> Miss chance and critical hits, even though I hate them. Default rates are defined in StoryInit, and custom rates can be defined in an action's definition. See the <code>accuracyCheck</code> and <code>critCheck</code> widgets added to "Damage and Formulas" for more details. By default, these are turned off (set to always-accurate and 0% respectively).</li>
<li>Added support for attacks with mulitple elements. To do this, assign an array of element names (as strings) to the action's <code>element</code> property. Set the <code>AVERAGE_ELEMENTS</code> variable in StoryInit to determine if you want the calculation to average each element, or to just find the best one.</li>
<ul><li>To demonstrate this new feature, Artist has gained four new attacks with multi-element properties.</li></ul>
<li>Improved the getter for Actions' <code>element</code> property. It will now log an error if the element is not in the element list, and it will return <code>null</code> if an illegal datatype was assigned to the property.</li>
<li>Renamed the <code>invisible</code> property of Actions to <code>silent</code>.</li>
<li>Element messages no longer have hardcoded trailing spaces. The trailing space is now automatically added to all element messages in <code>damageCalc</code>.</li>
<li>Renamed the <code>min_dmg</code> variable with ALLCAPS for consistency with other constant variables.</li>
<li>Renamed <code>call</code> to <code>callEncounter</code> for clarity.</li>
<li>You can now specify the splash damage cut value and provide an extension for <code>grenade</code>.</li>
<li><code>grenade</code> renamed to <code>splashDamage</code>.</li>
<li>Added specification notes to Action properties.</li>
</ul>
Version 1.16.1
<ul>
<li>Moved the encounters database to passages-unique.</li>
<li><code>randomtarget</code>, <code>echodamage</code>, <code>damagecalc</code>, <code>addeffect</code>, and <code>dispeltarget</code> have been renamed with camelCaps for better consistency and clarity.</li>
<li>Renamed the <code>cure</code> function to <code>removeEffect</code> and added modifiers to specify removal of ailments only, buffs only, or all effects.</li>
<li>It is now possible to pass an "all" selector to targeting functions, which will cause the action to affect all characters on the battlefield (puppets and enemies).</li>
<li><code>Actor.removeEffect</code> now has an argument that enables the removal of all instances of a stackable effect.</li>
<li>Debuff cure items now remove all instances of their respective debuffs.</li>
<li>Fixed a bug in the effect of Canned Air.</li>
<li>Fixed several bugs in <code>removeEffect</code>.</li>
<li>Tweaks made to <code>Actor.removeEffect</code> and <code>Effect.decay</code> for more consistent line spacing.</li>
<li>Several tweaks made to action functions to enable better chaining.</li>
<li>Several actions have been rewritten to use action functions instead of the original custom code.</li>
</ul>
Version 1.16
<ul>
<li><b>NEW FEATURE:</b> Shock cures. It is now possible for direct damage to cure certain status effects. Add a "shock" property value to the status effect to enable this. It can be either <code>true</code> for a guaranteed cure every time, or an integer value between 1 and 100 equal to the chance of cure <s>if you're a filthy randomizer</s>. None of the default effects use this feature at the present time.</li>
<ul><li>As a corollary, you can now set a <code>noShock</code> property for actions if you do not wish for them to trigger shock cures.</li></ul>
<li>File extensions have been converted from <code>.twee</code> to <code>.tw</code> to enable compatibility with Atom's SugarCube interpreter. Hopefully this will not cause problems.</li>
<li>Updated documentation.</li>
</ul>
Version 1.15.2
<ul>
<li>Moved the puppets definition from the default <code>StoryInit</code> to the custom passage.</li>
<li>Updated documentation for changes to the effect adder.</li>
</ul>
Version 1.15.1
<ul>
<li>Fixed an error that prevented actions with <code>displayname</code> from working as intended.</li>
</ul>
Version 1.15
<ul>
<li><b>NEW FEATURE:</b> Turn exchange. If this variable is set in <code>StoryInit</code>, an enemy will automatically act after every player action. This is useful if you don't want players ganging up on enemies.</li>
<li><b>NEW FEATURE:</b> You can now set any action as a puppet's default action in the status menu, provided it does not have the <code>noDefault</code> property.</li>
<li><b>NEW FEATURE:</b> You can now customize the appearance of your save game displays! See the documentation and the <code>saves-modifier.js</code> file for details.</li>
<li>There is now a link to the party menu on the sidebar, in case the hotkeys don't work.</li>
<li>The commands menu will no longer continue to display after selecting a command. This should hopefully make playing with the mouse a little easier.</li>
<li>The functionality of number hotkeys has been moved to a widget, <code>numKey</code>, and can be found in <code>Widgets (Misc)</code>. This will allow you to change the behavior of all number keys by just altering one block of code.</li>
<li>The "last action" notification in battle now says what the last action is, and does not display at all if there isn't one.</li>
<li>The action list display has been revamped. By default, it now displays only the action names and their EN cost, with additional information appearing on mouseover. (For mobile users, you can return to the old display through an option in the Settings menu.) Additionally, the action list is now contained to a fixed height, and gains a scrollbar if the text exceeds that height. (This should prevent it from overflowing the viewport on most monitors.) This can be changed by modifying the <code>actionlist</code> ID in <code>battle display.css</code>.</li>
<li>Fixed some unterminated class and function expressions.</li>
<li>Fixed several bugs in the action functions that used <code>findTarget</code>. They should now work more intuitively, and you no longer need to wrap them in a double-function call on the database end.</li>
<li>Protector should work again.</li>
</ul>
Version 1.14.3
<ul>
<li>Fixed an error in <code>applyEffect</code>. Single-target actions should no longer hit the whole party.</li>
<li><code>addEffect</code> will now generate an error message if no arguments are passed to it.</li>
<li>Knocked Down applications should no longer create an error.</li>
<li>Knocked Down should now correctly <i>decrease</i> Defense, instead of increasing it.</li>
<li>Fixed some text glitches in the previews of "struggle" and "rest" actions.</li>
<li><code>actions_this_turn</code> should no longer generate an error when an enemy uses an ally-targeting skill.</li>
<li>Exclusive effects should now be properly applied again.</li>
</ul>
Version 1.14.2
<ul>
<li>It is no longer possible to set an action's uses to more than its max uses.</li>
<li>Fixed a naming error in the function for resetting action cooldown. Cooldowns should be correctly reset now.</li>
<li>Added <a href="https://daneden.github.io/animate.css/" target="_blank">animate.css</a>.</li>
<li>The background music now requires the user to click a link instead of playing automatically.</li>
</ul>
Version 1.14.1
<ul>
<li>The engine is now completely independent of Twine! The <code>StoryData</code> passage is now used to store necessary project data. Remember to edit it when making your own project. (Remove the <code>StorySettings</code> passage to generate your own IFID.)</li>
<li>Removed <code>Effect Adder-modded</code>.</li>
<li>Fixed several errors in the effect adder and the <code>Action.displayname</code> getter.</li>
</ul>
Version 1.14
<ul>
<li><b>NEW FEATURE:</b> Loss-of-control effects. These effects will force your puppets to attack randomly at the start of a round, like "berserk" or "confusion" effects in most RPGs. There are now three new effects in the effect database to let you test this for yourself: "Hatred" forces the puppet to attack enemies, "Charmed" forces the puppet to attack its allies (potentially including itself), and "Confusion" can make the puppet target anyone on the field.</li>
<li><b>NEW FEATURE:</b> Battle grids. You can now display parties in a 3-by-3 grid on the battlefield. By default, characters in higher rows will guard lower ones, protecting them from direct attacks; you can implement other features as well. Set the <code>BATTLE_GRID</code> variable to <code>true</code> in StoryInit to enable this feature, but mind the instructions in the documentation. An additional menu pane for altering the formation of your characters has also been added. Helper functions have been added to <code>1_support-functions.js</code> to assist, allowing you to easily call parties excluding empty slots. (Thanks to A Friendly Irin for providing this code.)</li>
<li><b>NEW FEATURE:</b> Cooldown! You can now set a cooldown (and warmup) property for actions.</li>
<li><code>enemytarget</code> has been tweaked for compatibility with loss-of-control effects. As it can now be called by puppets as well, it has been renamed <code>randomTarget</code> for clarity.</li>
<li><code>echoDamage</code> has been tweaked to avoid triggering counters and damage reflection if an attacker targets itself with an attack.</li>
<li>Mark and Hunter attacks will no longer trigger if the hunter is uncontrollable.</li>
<li><code>refreshPuppets</code> will now automatically remove sticky effects.</li>
<li>The <code>trigger</code> property has been tweaked: It is now designed as a function that returns <code>true</code> if the counter should trigger, offloading functionality into the specific counters themselves. By default, it now returns <code>true</code>, meaning an undefined trigger will always activate the counter.</li>
<li>Added new <code>formula</code> property for actions, allowing users to specify unique damage formulas.</li>
<li>Added new <code>useSpecial</code> property for actions. This is a number between 0 and 1 that determines the proportion of base damage affected by the Special stat as opposed to the Attack stat. By default, this is set to 1 for attack items. See the updated <code>damageCalc</code> function for details.</li>
<li>Actors' stat getter function now returns 0 if the stat could not be found. This ensures that your game will run smoothly with the new damage calculation code even if you do not use the Special stat.</li>
<li>Offloaded the special functionality for Exacerbate and Downfall into functions tied to the actions themselves. This, combined with the <code>useSpecial</code> functionality, has made the "damageCalc special actions" passage obsolete, and it has been removed.</li>
<li>Cleaned up the action getters by making the lookup with <code>displayname</code> part of the basic <code>actionData</code> getter instead of manually inserting it into each getter.</li>
<li>Defined a "wait" action for enemies that allows them to delay their turn until later in the priority order.</li>
<li><code>actions_this_turn</code> is now a property of the battle controller, and tracks the actions of <i>all</i> the enemies during the round, not just the current enemy. This allows you to have enemies react to other enemies' behavior.</li>
<li>New widget: <code>populateEnemies</code>. This allows you to more quickly generate enemy parties by just passing the names of each enemy. (Thanks to A Friendly Irin for providing this code.)</li>
<li><code>massAttack</code> has been improved with support for row, column, and adjacent attacks if the battle grid is enabled.</li>
<li>Added new <code>pushAttack</code> action function that moves characters on the battle grid.</li>
<li>Added getters for Actors' row and column locations.</li>
<li>Support functions such as <code>getActor</code> have been split into a separate file, <code>1_support-functions.js</code>. Several functions for the aiding of battle grids have been added, plus a function that converts numbers to words.</li>
<li>The status pane now scrolls vertically with the user's viewport.</li>
<li>Fixed a problem with some of Cleric's abilities.</li>
<li>Status buttons now have a small left margin to give them a buffer against long character names.</li>
<li>Fixed an error in <code>playMusic</code>.</li>
</ul>
Version 1.13.2
<ul>
<li>Getters for Effects have been remade with greater specificity. It is now possible to override Boolean flags with <code>false</code>.</li>
<li>The "persistent" property for effects is now split into <code>persistAfterDeath</code> and <code>persistAfterBattle</code> for greater modularity. <code>refreshPuppets</code> now only removes effects that do not <code>persistAfterBattle</code>.</li>
<li><code>Effect.decay</code> should now be able to remove sticky effects.</li>
<li><code>refreshPuppets</code> now only refills action uses if the <code>actionRefillAfterBattle</code> variable is <code>true</code>. By default, it is set to <code>true</code>.</li>
<li><code>deathcheck</code> will now remove sticky effects from defeated characters.</li>
<li>Added links to SugarCube language definitions and Chapel's custom macros in the installation help page.</li>
</ul>
Version 1.13.1
<ul>
<li>Added a section in Design discussing targeting systems.</li>
<li><code>endofbattle</code> now unsets the battle controller and the enemy party variable to reduce overhead.</li>
<li>Added <code>fight</code>, <code>playMusic</code>, and <code>clearMusic</code> macros (thanks to A Friendly Irin for providing this code), and the <code>typewriter</code> macro to <code>Widgets (General)</code>.</li>
<li><code>addItem</code> now has a handler preventing item stacks from going over an <code>ITEM_MAX</code> variable. By default, this is set to 9.</li>
<li>Added the <code>itemDrop</code> macro from <i>Cartoon Battle</i>. Sorry, I thought I already added this one!</li>
<li>Implemented support for enemy item drops.</li>
<li>The variable names for struggle cost, berserker factor, and defender factor have been changed to allcaps for consistency with other constant variables.</li>
<li><code>LevelUps</code> is now initialized to an empty array in <code>StoryInit</code>. This should prevent later errors.</li>
<li><code>refreshPuppets</code> has been moved back to the default end of battle code, but has been given additional handlers if lasting damage is desired.</li>
<li><code>deathcheck</code> now adds the entire enemy object to the <code>kills</code> array, instead of just the name. This will allow you to access more attributes of defeated enemies as desired.</li>
<li>The info text for Defender now correctly refers to the defend factor, not the berserk factor.</li>
<li>Updated documentation.</li>
</ul>
Version 1.13
<ul>
<li><b>NEW FEATURE:</b> Aggro targeting! There is now a system for making enemies preferentially target characters based on how much damage they've inflicted. Set the <code>THREAT_TARGETING</code> variable to <code>true</code> in <code>StoryInit</code> to activate it. (Thanks to A Friendly Irin for providing this code.)</li>
<li><b>NEW FEATURE:</b> Limited-use actions. You can now define an action as having a hard limit of uses per battle. By default uses are refilled after battle, but you can change this if you wish. (Thanks to A Friendly Irin for providing this code.)</li>
<li>The <code>enemytarget</code> widget has been streamlined and modified to incorporate threat targeting.</li>
<li>The name of your game's currency can now be set in <code>StoryInit</code> and will display any time currency is referenced. By default, the name is "GP".</li>
<li>Added getters for assigning XP and GP rewards to enemies.</li>
<li>Added getters for <code>growthRates</code> and <code>StatTable</code> for Puppets.</li>
<li>Added a getter and setter for the <code>dead</code> property that automatically resets respawn and threat values when appropriate.</li>
<li>Added a Puppet method function: <code>hasAction</code>. This checks if the Puppet has an action of the given name.</li>
<li><code>deathcheck</code> now adds defeated enemies' XP and GP rewards to the battle total.</li>
<li>Puppets now have kill and defeat counters that are incremented in <code>deathcheck</code>. They aren't currently displayed anywhere, but they're there if you want to use them.</li>
<li>The party menu now displays the player's GP.</li>
<li>Implemented a more robust victory handler. The victory screen now displays XP and GP rewards, displays the effects of any level ups, and auto-forwards the player to a passage that can be specified in the encounter definition but defaults to the passage the battle was entered from. (Thanks to A Friendly Irin for providing this code.)</li>
<li>The default level up handler is now neater and more robust.</li>
<li><code>levelRate</code>, <code>growthRates</code>, and <code>StatTable</code> have been changed to database properties, accessed through Puppet getter functions.</li>
<li><code>endofbattle</code> now sets a flag recording that the encounter was fought. You can access these flags through the <code>encounters</code> variable.</li>
<li><code>deathcheck</code> will now remove effects only if they <i>aren't</i> persistent, not only if they are.</li>
<li>The check for legal action use has been moved from the hotkey code into the {{{<<actionLink>>}}} macro directly.</li>
<li>The Q key now triggers the link in the confirm phase rather than just forwarding the player to the action phase. This is useful if you want the confirm link to perform additional code.</li>
<li>The code for <code>actionlist</code> now looks a bit neater.</li>
<li>Fixed a broken link in Design.</li>
<li>Updated documentation.</li>
</ul>
Version 1.12
<ul>
<li><b>NEW CHARACTER:</b> The Artist. This character was created purely to explore the elemental affinities system, and is not intended to be balanced. Try them out on the Mystery Twins!</li>
<li>Actors have now been converted to a database structure. This should make runtime more efficient and facilitates the construction of in-game lists such as a Pokedex or bestiary.</li>
<li>Implemented "advance turn" functionality that allows you to space out enemy turns within a round. (Thanks to A Friendly Irin for providing this code.)</li>
<li>Implemented support for "ambush" encounters where the enemy takes the first round.</li>
<li>Implemented support for "full-round" enemy actions that take up all of the enemy's remaining attacks, as well as a corresponding getter function in the Action class.</li>
<li>Implemented support for damage reflection. (Thanks to A Friendly Irin for providing this code.)</li>
<li>Implemented support for counterattacks. (Thanks to A Friendly Irin for providing this code.)</li>
<li>Implemented support for puppet respawns.</li>
<li>Added a new Stat subclass, <code>FillStat</code>. This class is for stats that contain both a current and maximum value that can be exhausted and refilled, such as tolerances.</li>
<li>Rehauled action functions with greater functionality and documentation. In particular, the <code>massAttack</code> function can now function as a vanilla area-of-effect attack. <b>Note the warning in the new <code>findTarget</code> function.</b></li>
<li>Added a <code>heal</code> function, for those weirdos who want players to be able to recover HP.</li>
<li>You can now mask the stats of enemies in the status screen by flagging a <code>maskstats</code> attribute.</li>
<li>Main stats are now kept in a generic object, not a Map. This should make them more accessible in code.</li>
<li><code>setHP</code> depreciated; the <code>hp</code> property now has a proper getter and setter.</li>
<li><code>damageCalc</code> now accepts a custom target like <code>echoDamage</code>.</li>
<li>Added several additional getters for Action objects.</li>
<li>Implemented a getter and setter for the <code>en</code> property that bounds it between 0 and <code>maxen</code>. This allowed for the removal of the loop that otherwise accomplished this in PassageReady.</li>
<li>Implemented a new method function for <code>Actors</code>: <code>hasEquipped</code>. This will return <code>true</code> if the character has the equipment specified in the argument. (Thanks to A Friendly Irin for providing this code.)</li>
<li>Moved the code for displaying player commands to its own passage for greater modularity.</li>
<li>Offloaded elemental hit messages to a variable defined in {{{StoryInit}}} so users can more easily modify them.</li>
<li>Hunter counterattacks will now only trigger once for enemies that take multiple actions in the default code.</li>
<li>Prone characters should be able to be selected again.</li>
<li>Fixed a glitch that prevented buffs from displaying their removal message.</li>
<li>Elemental immunities should now actually result in 0 damage rather than the minimum damage value.</li>
<li>Actions should no longer be disabled if a character has 0 HP and is somehow still acting unless the action has a defined <code>hpcost</code> value.</li>
<li>Added a section in Design explaining HP and hit-to-kill ratios.</li>
<li>Updated documentation to reflect changes.</li>
</ul>
Version 1.11.1
<ul>
<li><b>Enemy attack order can now be customized.</b> Instead of enemies always acting in index order, you can give them a <code>priority</code> property to determine who acts in what order. Enemies will act in ascending priority value. Enemies will still act in index order by default.</li>
<li><code>hunterCheck</code> now resets the active target, subject, and action back to the originals after the counter is finished. Additionally, support for enemy hunters and multiple hunters within each party has been added.</li>
<li>The call to <code>hunterCheck</code> now takes place in "custom end of action effects", for greater modularity and to facilitate enemy hunters.</li>
<li>Support for enemy instant actions has been added: If an enemy uses an action with the <code>instant</code> property flagged to <code>true</code>, it will not end their turn. Obviously, be careful to avoid infinite loops with this.</li>
<li>Enemies with multiple attacks will no longer continue to act if they die or are stunned in the middle of their turn.</li>
<li>New property for effects: <b><code>persistent</code></b>. If flagged <code>true</code>, the effect will persist even after the character is defeated.</li>
</ul>
Version 1.11
<ul>
<li>Redid the getter functions for Boolean attributes of actions. They are now more robust, and will correctly return <code>false</code> or <code>null</code> if you set the override property to that value.</li>
<li>Added a new getter for actions: <code>displayname</code>. This is used for when you want the name that displays in the action list to be different than the name given in the code. (Thanks to A Friendly Irin for providing this.)</li>
<li>The mute button now uses icons rather than the word "MUTE". These icons were taken from <a href="https://material.io/" target="_blank">Material Design</a>.</li>
<li>Battle styling is now tied to a string variable, <code>B.style</code>, rather than Booleans. If you enter a string for <code>B.style</code>, the page will gain that class.</li>
<li>Battle styling code has now been moved to the main "Preparation" passage.</li>
<li><code>justeffect</code> and <code>dmgandeffect</code> have been condensed into one function, <code>applyEffect</code>. The code has been reworked to more easily accommodate applying multiple effects at once: just pass an array of names to the "type" parameter instead of a single string. Additionally, a new parameter has been added that can be flagged to make the applied effects pierce tolerance.</li>
<li>New action function: <b><code>multihitCustom</code></b>. This function makes it easier to implement more complex multi-hit abilities. Look at the action functions code to see it in full.</li>
<li>New helper function: <b><code>deadCount</code></b>. This returns the number of puppets currently flagged dead. It is located in 0_config.js.</li>
</ul>
Version 1.10.4
<ul>
<li>So apparently, all this time I forgot to make enemy protectors actually protect people. This has now been corrected.</li>
</ul>
Version 1.10.3
<ul>
<li>Status effects will no longer use an incorrect plural for their remaining duration if the duration is only 1 turn.</li>
<li>Ending a dark-styled battle will no longer result in a "flicker" before transfering to the next passage. (Thanks to A Friendly Irin for figuring this one out.)</li>
<li>Re-applying an effect should no longer produce a blank line.</li>
</ul>
Version 1.10.2
<ul>
<li>Fixed a bug in the command functionality from the 1.09 update. Command buttons should no longer link you to the wrong characters' actions.</li>
<li>It is now once again possible to target allies with ally-targeting skills.</li>
<li>Updated the link to the Tweego installer in the installation guide. It should hopefully link to a valid page now.</li>
<li>Steven should no longer generate an error when he's defeated.</li>
<li>The Crystal Gems will now protect Steven as they do in <i>Cartoon Battle</i>.</li>
</ul>
Version 1.10.1
<ul>
<li>Modified <code>addEffect</code> for better readability and versatility. It will now bypass the power calculation branches if you do not use the Special stat in your game, preventing potential glitches. It has also been changed to a proper skeleton model with specific <i>Cartoon Battle</i> elements removed, though this means it is overwritten by a modified version in passages-unique that will have to be removed if you want to make changes.</li>
<li>The quit button is now displayed in battle again. It is now tied to <code>actorlist puppets</code> rather than <code>commands</code>.</li>
<li>Added the championship enemies to the enemy database, if you'd like to see how their AI was constructed.</li>
<li>Removed some console logs in the code that were left over from testing.</li>
<li>Updated documentation to reflect changes.</li>
</ul>
Version 1.10
<ul>
<li>HP regeneration and elemental affinities now have support for both flat and percent-based attributes. Status screens have been updated to display both types, but if you don't plan to use flat rates, you can hide them from the display by modifying the <code>SOAK</code> and <code>FLAT_REGEN</code> variables in StoryInit. <b>You will need to remake any Actor objects for earlier saves to be compatible with this update.</b></li>
<li>Added new Actor method functions:</li>
<ul>
<li><code>regenHP</code>: Runs flat and proportional regeneration simultaneously.</li>
<li><code>getElement</code>: Allows you to specify whether you are getting the flat or porportional elemental affinity. You may also leave the second argument blank to get the raw object.</li>
</ul>
<li>The Map versions of <code>addMod</code> and <code>removeMod</code> now include support for an additional argument specifying whether the mod should be applied to flat or proportional.</li>
<li>Items now use object cloning for <code>clone</code> and <code>toJSON</code>. This should correct a bug with unequipping stacked accessories negating the mods from all accessories.</li>
<li>Menu widgets have been moved to the <code>party menu.twee</code> file for easier reference. The previous file has been renamed to <code>Widgets (Misc)</code> and contains widgets for status pane displays.</li>
</ul>
Version 1.09.1
<ul>
<li><code>addEffect</code> now works when passed an effect name, not just an Effect object. You can use it just like the Effect constructor: name first, then duration, then power.</li>
<li>Fixed a potential error that could occur if <code>chain</code> attempted to run with an undefined target, subject, or actor.</li>
</ul>
Version 1.09
<ul>
<li><b>Rehauled battle character displays.</b> It should now be possible to have party sizes extend to multiple lines without issue. The command panes will no longer be displayed in the initial battle map; instead, you will select a character by clicking on their name or pressing their hotkey, and the party display will be collapsed to just them and their command pane. Done/Stunned notifications will now display at the bottom of the actor boxes instead of in the command panes.</li>
<li>The cancel button is now R, to avoid interference with the E key also being the item command hotkey.</li>
<li>During the targeting phase, the party not being targeted will now be hidden from view.</li>
<li>The battle map is now hidden during the command phase.</li>
<li>New feature: <b>automatic turn ending</b>. Enable this in the settings menu to end your turn automatically when all characters have acted.</li>
<li>Fixed a text glitch in Focus.</li>
</ul>
Version 1.08.5
<ul>
<li>UI text should now display correctly even if the default text color has been changed through in-game modifications.</li>
<li>Effects with no removal text will no longer force the player to click through the end-of-round passage.</li>
<li>Actions now have a getter for the <code>nosave</code> property.</li>
</ul>
Version 1.08.4
<ul>
<li>The default add and remove text for effects should now display correctly.</li>
<li>Protector should no longer create an error message on removal.</li>
<li>Actions that disallow self-targeting should now work correctly; the property has been renamed to <code>noself</code> for greater clarity, and an appropriate getter has been added.</li>
<li>The setup for the damage formula type and min stat values has been moved to the custom StoryInit section; if that assignment is in the default, min stat values won't auto-update if you change the formula in the custom StoryInit.</li>
<li>Added a <code>RESPAWN_HP</code> variable you can set in StoryInit. This will determine the proportion of HP a respawned character revives at. By default, it is set to 1.</li>
<li>Moved the special checks passage to passages-unique.</li>
<li><code>actionLink</code> now works for items as well.</li>
</ul>
Version 1.08.3
<ul>
<li>Moved hotkey definitions to their own passage for greater modularity and neatness of code.</li>
<li>The call to the Special stat for item usage in the damage formula has been moved to "damageCalc custom factors" for greater modularity, in case you don't want items to use the Special stat (or don't want to have a Special stat at all).</li>
<li>The code that runs when selecting an action has been outsorced to a widget, <code>actionLink</code>. This widget is now called for both the link itself and the hotkey shortcuts for actions, enforcing consistency between the two.</li>
<li>Action's <code>toJSON</code> now clones the entire object instead of creating a new object with just the name. This is useful if you want unique action properties to persist.</li>
<li>Fixed a typo in Battle Phases. Targeting should now work correctly if an enemy is both untargetable and a martyr.</li>
</ul>
Version 1.08.2
<ul>
<li>Equipping a new item to a filled equipment slot will now replace the last subslot rather than the first. I felt this was more intuitive.</li>
<li>Mass status effect abilities now assign the current target to <code>$B.target</code>. This helps if you want to do something that depends on that.</li>
<li>The action phase no longer checks to see if {{{$action.act()}}} evaluates to <code>null</code>. This was causing a problem where the <i>entire</i> function was being run in the check, not just evaluating the result. Actions were effectively run twice if they contained anything before the <code>return</code> statement. If you want the box to disappear, you will need to set the <code>act</code> property itself to <code>null</code>.</li>
</ul>
Version 1.08.1
<ul>
<li>It is now possible to equip multiple items of the same type. Puppets now have 2 Accessory slots, and you can test this functionality for yourself with the new "Color of Defeat" item.</li>
<li>The unequip buttons have been removed from the full party view due to the discovery of a bug involving them.</li>
</ul>
Version 1.08
<ul>
<li>Actions and effects have now been converted to Flyweight format. This leads to a few changes in how certain attributes are defined; see documentation for details.</li>
<li>Constants such as <code>$STATUS_SCREENS</code> have been changed to attributes of <code>setup</code>. This will allow changes to persist through game version changes.</li>
<li>Stat mods have been completely redone, such that changes to equipment will now correctly carry over between save game version changes. (Thanks to Akjosch for helping me with this.) <b>This change is likely not compatible with previous versions unless you completely remake your <code>Actor</code> objects.</b></li>
<li>The menu screens no longer have a border. I felt this was more aesthetically pleasing.</li>
<li>The <code>unequip</code> function must now be explicitly passed a <code>destroy</code> value of <code>true</code> to destroy equipment, instead of any truthy value.</li>
<li>The Status pane of the party menu now uses a widget, <code>statusDisplay</code>, to display the common portion of the normal and detailed status displays. The widget can be found in <code>Widgets (Menu)</code>.</li>
</ul>
Version 1.07
* <b>The party menu has been rehauled to include the full functionality expected of a typical RPG!</b>
** Added a status screen that shows the party's HP, MP, and experience levels. Clicking on one character will provide detailed stat, equipment, and ability information.
*** The detailed status screens are tied to the {{{$STATUS_SCREENS}}} variable, which has been changed to a generic object with two attributes to distinguish between the status screens visible in the menu and the status screens visible in battle. <b>If you have already made a project with an earlier version of the engine, you will need to update this variable in your {{{onLoad()}}} function to avoid errors in existing save games.</b>
*** Stat objects now contain tooltips that explain what the stat does. These can be seen in the detailed status pane.
** Added an inventory screen that lists all inventory items and allows you to view detailed information about each.
** The equipment screen has been redesigned to give a nicer appearance more consistent with the new status screen. The unequip buttons now have consistent positions, as well.
** The menu navbar has now been changed to a vertical display on the right, instead of a horizontal display across the top. This frees up more room for the status screens.
** You can add additional screens modularly by adding entries to the {{{$MENU_OPTIONS}}} variable. <b>If you have already made a project with an earlier version of the engine, you will need to update this variable in your {{{onLoad()}}} function to avoid errors in existing save games.</b>
* Music can now be muted and unmuted with the M key.
* A peppy theme tune now plays while navigating the help file to show off the sidebar music display. Don't worry -- you can mute it with the newly-added hotkey if you prefer silence!
* Added an entry on designing the AI for <i>Cartoon Battle</i>'s Bonnibel in the documentation section.
* Added a note about damping in divisive damage systems in the design section.
* Fixed a typo that created a fatal error in the level up data for puppets.
* Changed the way status screen customization works to be more modular. Cycling is now tied to the length of the {{{$STATUS_SCREENS}}} array, and the displayed pane is based on the content of the array element rather than hardcoded to specific {{{$stScreen}}} values.
* The structure of the item database has been reworked to incorporate Flyweight design principles. This should hopefully speed up the game. (Thanks to Akjosch for help with this.)
Version 1.06.4
<ul>
<li><code>custom end of action effects</code> is now called at the end of enemy actions, not just player actions. This allows you to have events related to enemy actions as well.</li>
<li>Added a default music display in the UI bar should you wish to use it. Remember to properly attribute your media if you are using it under a Creative Commons license.</li>
<li>Protector SHOULD now be removed when the protected character dies, for real this time, probably, maybe, or possibly this bug will continue to be the bane of my existence. We'll find out.</li>
<li>Enemies will no longer perform glitched actions if all puppets are dead when they attempt to find a target.</li>
</ul>
Version 1.06.3
<ul>
<li>Fixed the party selection glitch. You can no longer add an extra puppet to the active party by double-clicking on an active puppet.</li>
<li>Changed the party selection screen to display the reserve in the main pane instead of the sidebar.</li>
<li>Respawn mechanics added. If you give a character a <code>respawn</code> value at creation, it will be decremented every round. When it hits 0, the character will be revived in <code>endofround</code>. By default, characters are revived to full HP, but this can be changed.</li>
<li><code>endofbattle</code> now has a modular component. The calls to <code>refreshPuppets</code> and <code>restock</code> have been moved there to more easily remove them if they are undesired.</li>
<li><code>newturn</code> now has a modular component. The code for Energy regeneration has been moved there to more easily enable switching to a different type of resource system.</li>
<li>Passage jump actions now work correctly.</li>
<li>Tweaked the actor blocks slightly. Their borders are now twice as thick; only the borders are highlighted upon selection; and the selection color is now a darker blue. This will hopefully result in selection looking less garish, especially in dark-themed passages.</li>
<li>Action phase modularity is now more robust; the components will now be hidden both in the case that the value is <code>null</code> <i>and</i> in the case that the value is a function that evaluates to <code>null</code>.</li>
<li>The checks for Protector and Martyr in the targeting logic have been compartmentalized into their own widgets.</li>
<li>Added documentation on delayed attacks.</li>
</ul>
Version 1.06.2
* New circumstantial modifiers for enemies:
** <b>hidden:</b> If you flag this attribute, the enemy won't get a stat block generated in <code>actorlist</code>, making it invisible to the player. Note that this means there is no way to target it, either. Hidden enemies are not excluded from the victory condition check, so if you use this, you should make a special victory condition or give the player some way to damage the enemy.
** <b>immortal:</b> If you flag this attribute, the enemy will not die when they are killed. They'll still produce a death message, but their "dead" flag will not be flipped to <code>true</code>. This is useful if you want to replicate a feature like the final battle of <i>EarthBound</i>, where the enemy cannot be defeated through normal means.
** <b>fakedeath:</b> If you flag this attribute, the enemy will not take an action during their turn (even to say they are stunned), as if they were defeated. Effect decay will still occur as normal, however. This is useful if you want certain enemies to "play dead" or just if you want them to skip turns.
* Defeated enemies will now produce no death message if you set their <code>deathMessage</code> attribute to <code>null</code>.
* The <code>removeEffect</code> function now has a shortcut for removing a specific type of effect. If you pass it a string rather than an effect object, it will remove the first effect whose name matches the string. Note that this only removes the first instance it finds, so it won't clear multiple instances of stackable effects.
* You can now use actions to move the player to a new passage by setting the <code>passagejump</code> attribute to <code>true</code> and setting the <code>phase</code> attribute to the name of the desired passage. This is useful if you want to incorporate passage mechanics into your battles, such as allowing characters to interact with the objects in a room to produce changes to the battle.
Version 1.06.1
<ul>
<li>Removed Chapel's cycles system due to its potential to cause a fatal error.</li>
<li>Added hotkey support for all-targeting abilities.</li>
<li>Added a hotkey for party menu access, though the party menu is rudemantary at the moment.</li>
<li>Last action is now remembered upon selection, not just use. This should hopefully ease the pain of the Back button booting you all the way back to the start of selection.</li>
<li>Enemy reverse display is now handled through a battle variable, allowing it to be tracked more easily.</li>
<li>The special handler for Focus' cost reduction has now been moved to custom end of action effects, to prevent conflicts if you want to create your own action named "Focus".</li>
</ul>
Version 1.06
<ul>
<li><b>Hotkeys are in!</b> Use the number keys to select characters, the Q key to confirm, and the E key to cancel.</li>
<li>The story title area now displays white text as part of the default formatting style, making it easier to read with the dark UI bar. If you want to lighten the UI bar, such as with the normal Bleached style, you will probably want to change this.</li>
<li>Displaying the confirm phase is now optional. This can be toggled in the Settings menu.</li>
<li>Difficulty settings also moved to the Settings menu.</li>
<li>Piercing attacks now set effective defense to 1 instead of 0 if the {{{$formula}}} variable reads "divisive". This avoids a division by zero error in a divisive defense formula.</li>
<li>Special death handling no longer adds a special death passage to the queue if it is already in the queue; this should prevent the player being forced to see the special death passage multiple times if the enemy is killed through a multi-hit attack or other circumstance that triggers <code>deathcheck</code> multiple times.</li>
<li>Fixed the enemy Martyr bug: If an enemy martyr exists, you still have to select them in the targeting phase, but they will be the only viable target. (Additionally, this means you can no longer bypass Martyr with skills that target all characters -- you can still target allies, but the martyr will be the only possible enemy target.)</li>
<li>Martyr now overrides untargetable protection for enemies, as it does for player characters.</li>
<li>You can now select protected enemies as targets, though their protector will still cover them. This encourages players to pay attention to who's protecting whom!</li>
<li>Protector now displays the name of the protector's charge in the info box.</li>
</ul>
Version 1.05.1
<ul>
<li>Added documentation for health bars and the item shop.</li>
<li>Enemy health bars will now be correctly mapped to the number of enemies, rather than the number of puppets.</li>
<li>Enemy health bars are now a slightly darker red. (I felt the default red looked too garish next to the default green.)</li>
<li>Enemy and player health bar colors are now variables set in StoryInit.</li>
<li>Enemy status buttons should no longer display for defeated enemies.</li>
</ul>
Version 1.05
<ul>
<li>The engine now comes bundled with <a href="https://twinelab.net/custom-macros-for-sugarcube-2/" target="_blank">Chapel's custom macros.</a></li>
<li>Relatedly, the battle interface now displays health bars! If you prefer the old look, you can set <code>$SHOW_HEALTHBARS</code> to false.</li>
<li>Created an interface for an item shop! It's a little rudementary at the moment, but it is functional.</li>
<li>Restructured the version updating and save config code. It should hopefully work correctly now.</li>
<li>The HP display will now be hidden if you flag an enemy's <code>maskhp</code> attribute. Additionally, the <code>healing</code> attribute has been renamed to <code>showMaxHP</code> for clarity.</li>
<li>Added additional modularity in <code>damageCalc</code>: The damage formula definition is now a separate passage, as are handlers for actions with special behavior, such as Exacerbate and Downfall.</li>
</ul>
Version 1.04-beta
<ul>
<li>Retweaked the handling for subjects and targets <i>again</i>. They now get the ID value from the current subject/target object itself instead of storing it as a separate variable, which removes the need to call <code>getActor()</code> manually.</li>
<li>The battle passages have been tweaked to support additional modularity. Code for altering the actor list in special cases is now outsourced to another passage that can be edited separately from the main display code; the "Preparation" passage now calls to a separate passage for custom modifications; the Effect Adder outsources protection checks to a separate passage; and you can now set which status screens display in StoryInit.</li>
<li>Targeting AI now includes handlers for bypassing untargetable and protection clauses. Simply pass "ignore untargetable" or "ignore protection" as arguments to the widget, and those characters will be made viable targets.</li>
<li>Spread attacks now bypass untargetability, as in <i>Bonfire</i>.</li>
<li>The effect adder should now correctly identify when it's passed a number for effect power.</li>
<li>The battle controller now checks if an item was used during the round, if that's something you want enemies to react to.</li>
<li><code>SacrificeUsed</code> is now a property of the battle controller, for consistency.</li>
<li>The <code>$power</code> variable, used for constructing effects, has been changed to use a temporary variable instead.</li>
<li><code>victorycheck</code> is now run in the enemy phase. This accounts for the rare case where the player is defeated through damage over time; previously, <code>victorycheck</code> was only run during the player's turn, so the enemy turn would still play out even if everyone was already defeated.</li>
<li>Minor tweak, but it <i>really</i> bugged me: boss names are no longer misaligned with their HP display in the boss box. The display is still misaligned relative to the whole box because the status button still throws off the centering, but this is less noticeable.</li>
</ul>
Version 1.03-beta
<ul>
<li>The handling for the <code>$subject</code>, <code>$target</code>, and <code>$actor</code> variables has been redone to be more intuitive and allow for easier tracking of the original objects. Thanks to Discord member <b>Akjosch</b> for help and directions. See documentation for details.</li>
<li>Enemy cooldown logic is now more intuitive: They will be able to use attacks when the cooldown <i>is</i> zero, rather than requiring the cooldown to be negative. This means that the value you give to a cooldown will now be equal to the number of turns before it can be used again.</li>
<li>Fixed a bug with status effects that had entangled tolerance values (such as Curse and Forsaken). Tolerances should now be reduced and reset correctly.</li>
<li>Puppets are now revived as part of <code>refreshPuppets</code>.</li>
<li>Dispel targeting will now correctly check against the threshold value when targeting a martyr.</li>
<li>Additionally, Martyr, Defender, and Berserker will no longer count towards the threshold check.</li>
<li>Protector should now be correctly removed if the protected character dies, but I have not tested this. Please report any problems you encounter.</li>
</ul>
Version 1.02-beta
<ul>
<li>The default damage constant has been doubled to 80; this should speed up battles significantly. (Keep in mind DoT effects use the same constant.)</li>
<li>There is now support for enemies taking multiple actions per turn. Set the <code>noAttacks</code> attribute in an enemy object if you wish to use it. As this is a new feature, it may result in unforseen problems; please report any bugs you find.</li>
<li>There is now support for modifying the CSS of battles. See documentation for details. A "dark" format that returns to the default SugarCube style is provided in the CSS files.</li>
<li>The default style has been changed to a modified form of the Bleached style. The background is slightly less bright, links are easier to see on the white background, and UI messages will now be more readable.</li>
</ul>
Version 1.01-beta
<ul>
<li>The <code>massAttack</code> function will now default to the action variable's duration if no duration is passed as an argument.</li>
<li>Fixed a typo that prevented Berserker from being applied properly.</li>
<li>Dispel abilities (Restoration/Neutralize/Cleanse) should now work correctly.</li>
<li>Added a "user-defined variables" passage to be included in StoryInit. You can now keep your own StoryInit functionality completely separate from the engine's code, instead of needing to juggle multiple StoryInit files.</li>
</ul>
Version 1.0-beta
<ul>
<li><b>Equipment is in!</b> Party Picker updated with a page for equipment and a corresponding GUI. Make sure to grab the new and updated CSS files so they display properly.</li>
<li>New widget: <code>equipmentlist</code>.</li>
<li>New function <code>unequipAll</code> added to Actor class.</li>
<li>Items now consume 2 Energy when used.</li>
<li>Fixed a bug that allowed you to add extra characters to your party through the party picker.</li>
<li><code>actionlist</code> updated to display passive abilities when out of battle.</li>
<li>HP regeneration implemented. Puppets and enemies will both regain a percentage of their max HP based on their <code>HPregen</code> attribute at the start of their turns. You can play with this by equipping the "Color of Growth" item.</li>
<li>Effect descriptions are no longer bolded, and effect names are no longer underlined.</li>
<li>Updated documentation for new features.</li>
<li>Updated this changelong to display in reverse chronological order.</li>
</ul>
Version 0.99-beta
* <code>Inventory</code> objects now revive correctly, and <code>Item</code> objects will no longer be revived as <code>ItemActions</code>.
* Battle flow now works through <code>replace</code> macros instead of passage jumps. This should speed up battles considerably.
* Defeated characters will now have their command menus hidden instead of displaying an ugly blank box.
* Added a <code>longreturn</code> widget using the functionality described in the SugarCube v2 documentation.
Version 0.95
* Custom objects are now serializable, with custom <code>clone()</code> and <code>toJSON()</code> functions. <code>Actor</code>, <code>Action</code>, and <code>Inventory</code> are revived to exact copies of their old versions, while <code>Item</code> and <code>Effect</code> are revived to new versions using only their name ID. This allows changes made to the database to be incorporated when loading saves from an earlier version.
** A section on this has been added to the documentation.
* Rehauled inventory. It now functions like a Map, meaning you can just call up the item name to find something instead of using the <code>find</code> widget.
* Equipment is in, but untested. It <i>should</i> work, but no promises.
* <code>effectmanager</code> and <code>removeEffect</code> have been made obsolete by applying equipment logic to status effects. Effects now apply their changes through instance functions called on application and removal and defined in the effect database. Method functions for adding and removing effects have been added to the Actor class to facilitate this. Call them with a <code>print</code> statement to get the add/removal text, or use a <code>run</code> statement to mask it.
** Damage-over-time calculations are also now handled through instance methods defined in the effect database. Pass the character to the effect's <code>damage()</code> function to calculate how much damage they take. The function returns the damage value, so pair it with a <code>set $dmg =</code> to store it or a <code>print</code> to show the value (such as in the status pane).
*** The messages displayed when DoT is triggered have also been tied to effect definitions.
* setTol and getTol changed for more clarity and to facilitate modular changes from equipment. Setter is now both relative and absolute: if a tolerance doesn't exist, it will create one, and if a tolerance does exist, it'll add the passed value. Immunity is now handled through a third attribute that can be set by passing a Boolean for a value.
* Modified spell effects are now handled through the <code>spellMod()</code> attribute function rather than the "spell check" passage; see documentation for details.
* Added Actor method functions: setHP and setMaxHP.
* Status effect previews now provide the duration of the effect.
* Effect database link added to the documentation.
* Enemy AoE attacks that mimic player attacks will now target the player's party instead of the enemies.
* Corrected a key/value mixup in <code>decCD()</code>. Enemy cooldown should now work correctly.
* Enemy actions will no longer produce an error if they fail to find a target. (Thanks to Reddit user itaigreif for finding this.)
* Corrected a subjective/objective mixup in the pronoun object.
* Stat and elemental affinity displays are now right-justified for a more consistent appearance.
* All passages excepting the landing have been exported to twee files, because I got sick of having to wade through Twine 2 to edit things. Enjoy Ctrl+F.
* Fixed a typo in <code>deathcheck</code> that caused a fatal error.
* Fixed several mixups in surrender logic. Surrendering enemies should now work correctly.
Version 0.9
* Massive rehaul. Actions, puppets, enemies, items, and effects now properly defined in database JavaScript files. Many widgets offloaded to JavaScript functions, and battle passage code tweaked for greater modularity. Actions now display fully consistently with special formatting that looks much neater and more professional.
* Added support for elemental affinities and status effect tolerance.
* Passages moved further down in the story map to hopefully prevent overlapping of story files when importing the engine through compilers.
Version 0.57
* New puppets added: the Archer, the Cleric, and the Witch! These are all specialists with 50 in one stat and 20 in both others. Anyone who felt the battles took too long should try out Archer. They have not been balance-tested at all, so do tell of any hilarious gamebreaking situations you encounter.
* Action text added for all actions, and restructured the passage so it displays more consistently. A few other things have also been tidied behind the scenes to hopefully make the code easier to use.
Version 0.56
* New puppet added: the Bard! They are a jack-of-all-trades character designed to exploit the stackability of basic debuffs. I have not balance-tested them at all so please try them out.
* Relatedly: party member swap functionality is in! I plan to tweak it later but it currently works as long as your characters aren't modified in any way between swaps. (Thanks to greyelf for helping me with the visual elements.)
* Martyr is now removed if someone else uses it at the same time.
* Stun wearing off will no longer trigger an infinite loop.
* Tweaked the way some passages and widgets worked for more modularity and readability.
Version 0.55
* Completely rehauled the way stats are formatted: they are now all bound to a single Map object with sub-attributes for base values and temporary modifiers. Getter and setter functions added to enable easy access to these values.
* Effect manager now only runs when necessary instead of all the time! This will probably save your processor a lot of grief.
* Relatedly, Protector SHOULD finally behave properly.
* Documentation updated to reflect changes.
* Added new passage for discussing implementation of extra features.
* Added information about level up mechanics, and added example implementation for both standard level up mechanics and a point-buy system.
Version 0.54
* Added difficulty settings! Hard mode is the original targeting model. Easy mode will disable smart targeting altogether. Medium mode will still have smart targeting, but vulnerable characters will only get preferential chances of being targeted rather than being the only possible targets. Documentation will be updated when time permits.
Version 0.53.1
* Design notes on status effects and battle flow added.
* Model passages added to enable use of different battle flow systems.
Version 0.53
* Documentation actually updated to reflect changes.
Version 0.52.1
* Streamlined some things in backend to be more readable. Effect adder now filters by attribute like the effect manager does, and if statements in story JavaScript changed to switches. I think I fixed all bugs created by this change, but do tell me if you encounter anything.
* Pronouns added as attributes in the "Actor" class to make system text easier, like I alluded to in earlier versions of the documentation. Constructor must now take a gender (as a single uppercase character, F/M/N) as its final argument.
* Documentation updated to reflect changes.
Version 0.52
* Stat mod glitch should be fixed.
* Alert application now produces no message, as intended.
Version 0.51
* Protector no longer causes an error in PassageDone, and should be removed properly if the protected character dies.
Version 0.5
* Initial release.RPGs are pretty complicated games, and require a lot of thought in even their most basic elements. Here I'll discuss the reasoning that went into the choices I made when designing the engine, and some potential alternatives.
A look at the game <i><a href="http://bonfire-game.wikia.com/wiki/Bonfire_Game_Wiki" target="_blank">Bonfire</a></i> and the elements listed in its wiki may be instructive, as I purposefully designed the engine to emulate it in many aspects. The program RPG Maker VX Ace is also worth a look, as it has great transparency and modularity in many of its core design principles, as well as a community of designers you can use as a resource. For further reading, you could check out the website <a href="http://howtomakeanrpg.com" target="_blank">How To Make an RPG</a>, which touches on these topics and others. The YouTube series <a href="https://www.youtube.com/channel/UCqJ-Xo29CKyLTjn6z2XwYAw" target="_blank">Game Maker's Toolkit</a> may also be worth viewing, though it is much more general in the topics it covers.
<h1>Table of Contents</h1>
><a href="#dmgform">The Damage Formula</a>
>><a href="#dmgform.basic">The Basics</a>
>><a href="#dmgform.damper">Damping</a>
>><a href="#dmgform.defenses">Subtractive vs. Divisive Defense</a>
>><a href="#dmgform.special">Special Attacks and Weighting</a>
>>><a href="#dmgform.special.1">Lumped Weighting</a>
>>><a href="#dmgform.special.2">Specific Weighting</a>
>>><a href="#dmgform.special.3">Adding Constants</a>
>>><a href="#dmgform.special.4">Skill Progression</a>
>><a href="#dmgform.htk">HP and hit-to-kill ratios</a>
>><a href="#dmgform.random">Randomness and Variance</a>
>><a href="#dmgform.exta">Additional Reading</a>
><a href="#abilities">Designing Abilities</a>
>><a href="#abilities.basic">Before you begin, what are your regular commands?</a>
>><a href="#abilities.resources">Resources and Game Balance</a>
>>><a href="#abilities.resources.mana">Mana Points (Limited Resource)</a>
>>><a href="#abilities.resources.sta">Stamina (Renewable Resource)</a>
>>><a href="#abilities.resources.cd">Cooldown</a>
>>><a href="#abilities.resources.recoil">Recoil/Blowback</a>
>><a href="#abilities.elements">Elemental Effects</a>
>><a href="#abilities.heal">Healing</a>
><a href="#items">Item Design</a>
>><a href="#items.consumable">Consumables</a>
>><a href="#items.equipment">Equipment</a>
><a href="#effects">Status Effects</a>
>><a href="#effects.control">Hold Effects</a>
>><a href="#effects.loss">Loss-of-control / Uncontrollable Effects</a>
>><a href="#effects.statmod">Stat Modification Effects</a>
>><a href="#effects.dot">Damage Over Time</a>
>><a href="#effects.death">Instant Death Effects</a>
>><a href="#effects.protect">Effect Protection</a>
>><a href="#effects.player">Can players use them?</a>
>><a href="#effects.extra">Noteworthy Examples</a>
><a href="#targeting">Targeting and AI</a>
>><a href="#targeting.smart">Smart Targeting</a>
>><a href="#targeting.aggro">Aggro Systems</a>
<h2 id="dmgform">The Damage Forumla</h2>
The basic structure of an RPG is that characters exchange attacks, inflicting damage to wear down their opponent's HP. Well, how do we determine what those numbers will be? What scale are we operating on -- do you want the average attack to throw around numbers in the double digits, triple, quadruple? Most games have some sort of "attack" stat that increases damage, and a "defense" stat that mitigates it, but how exactly do we want them to interact?
The answer depends on you! Small changes to the damage formula can lead to big impacts in how the game plays and how the player interacts with it. The formula I designed for <i>Cartoon Battle</i>, and thus the default formula in this engine, looks like this:
<div class="formula">DMG = (setup.base + (setup.damper * a.atk)) * (weight) - (setup.damper * b.def)</div>
In this section, I'll go over each part of the formula, and give you examples of some alternative formulas you can use if you want a different type of game.
<h3 id="dmgform.basic">The Basics</h3>
The most obvious damage formula is simply Attack minus Defense:
<div class="formula">DMG = a.atk - b.def</div>
However, we run into a problem: if B's Defense is equal to or greater than A's Attack, the attack will do no damage at all. Most RPGs like to have all their stats operate on the same scale, but if you want attacks to do meaningful damage, you will have to consistently make Defense lower than Attack across the board. Maybe you're okay with that, but assuming you're not, how might we fix the problem?
Here's what <i>Bonfire</i>, and this engine, do:
<div class="formula">DMG = C + (a.atk - b.def)</div>
C here stands for "constant". It's a flat number that's the same for every attack. You can think of it as what you want your <b>basic damage</b> to be: how much damage attacks should do on average. If A's Attack and B's Defense are exactly equal -- so, if an average attacker hits an average defender -- the attack will do C damage. This makes it very easy to determine the scale of your battles: if you want big numbers, you can make C something like 200 or 1000; if you want small numbers, you can make C something like 10 or 20. <i>Bonfire</i>'s constant is 20; this engine's is 40 by default.
RPG Maker VX Ace takes a different approach:
<div class="formula">DMG = a.atk * 4 - b.def * 2</div>
This also creates positive damage values for equal Attack and Defense values. If A has 10 Attack and B has 10 Defense, the attack will do 20 damage. The scale of this formula is less immediately clear, but you can still get a pretty good handle on it through careful adjustment of your characters' Attack and Defense stats.
However, a big part of RPGs is leveling up, isn't it? Over the course of the game, your characters will get stronger and their stats will get bigger. This means you also have to consider how your damage formulas will <i>scale</i> at higher values.
Let's level up the characters in our example a bunch. A now has 50 attack and is fighting a monster with 50 Defense. What does the damage look like now?
With my formula, it's the same:
<div class="formula">40 + (10 - 10) = 40 DMG</div>
<div class="formula">40 + (50 - 50) = 40 DMG</div>
But now look at RPG Maker's formula:
<div class="formula">10*4 - 10*2 = 20 DMG</div>
<div class="formula">50*4 - 50*2 = <b>100 DMG</b></div>
That's five times as much damage as before, even though the difference between stats is still the same! What happened? Scaling. Attack gets multiplied by a value of 4, while Defense only gets multiplied by a value of 2. That means that every point of Attack will count twice as much as every point of Defense. Even if the two stats are the same, bigger numbers will lead to a bigger result. You can still make this balanced, but you will need to increase HP as well to compensate for the higher average damage.
This is actually likely what you want! Most RPGs make everything get bigger over time, including HP and damage values. That's a big part of the fun for most people: bigger numbers make the player feel like they're progressing and getting more powerful. If they get bigger stats and the end result still looks the same, they might be disappointed. RPG Maker assumes you plan to scale everything up over time, and makes its formula account for that.
<i>Bonfire</i> is a rare RPG that doesn't work this way. Nobody's HP changes as the game progresses, so damage values are meant to stay relatively constant. <i>Cartoon Battle</i> doesn't have progression mechanics at all, so I opted for the more intuitive <i>Bonfire</i>-style formula. An equal-scaling formula is better if you want your RPG to focus on a small number of tactical battles that don't scale up over time.
Note that these features aren't mutually exclusive! You can absolutely combine constants and variable weights. Something like...
<div class="formula">DMG = 40 + (a.atk * 4 - b.def * 2)</div>
...is also a valid option, and in fact that exact formula is used by RPG Maker as well, for something special we'll discuss later.
<h3 id="dmgform.damper">Damping</h3>
If you're afraid scaling might make damage values change too much too fast, you can add a <b>damper</b> to your formula. This is a value between 0 and 1 you can apply to your stat terms to make them scale at a slower rate. This might look like:
<div class="formula">DMG = C + D * (a.atk - b.def)</div>
To use a real example, <i>Bonfire</i> uses a damper of 0.6, so its complete formula looks like this:
<div class="formula">DMG = 20 + 0.6 * (a.atk - b.def)</div>
This means a character's Attack or Defense stats will need to change by 1.7 to change the final damage by 1 point. This will make stats have a smaller impact over time and/or compared to the constant value. You may want to do this if you want numbers to creep upward at a slower rate, or if you want stat modifiers (such as from equipment or status effects) to have less of an effect. As discussed in the next section, small changes in stats can have huge effects if you aren't careful!
In a <a class="noExternal" href="#dmgform.defenses">divisive defense system</a>, you would use a power instead, such as:
<div class="formula">DMG = (a.atk/b.def) ** 0.6</div>
(Note that the {{{**}}} operator is not compatible with all browsers. For greater reliability, use the {{{Math.pow()}}} function.)
Be careful with this, though -- powers can lead to unpredictable results at extreme ranges! I don't recommend using large or small powers unless you want huge damage variance.
You can also change the damper to an amplifier by giving it a value greater than 1, if you want to make your formula more volatile instead.
<h3 id="dmgform.defenses">Subtractive vs. Divisive Defense</h3>
So far we've been looking at what happens when everyone's stats are equal. But what if they're different? You might want a character who's a heavy hitter, or an enemy who's designed to be easily defeated. Let's look at what happens when it's 20 Attack vs. 10 Defense, using RPG Maker's formula:
<div class="formula">20 * 4 - 10 * 2 = 60 DMG</div>
Woah! We only doubled the Attack value, but that came out to <b>three times</b> as much damage! What happened here?
The answer is that we used <b>subtractive defense</b>. When damage is based on an absolute difference between stats, small changes can have big effects, and the end result is highly dependent on characters' stats. To simplify things a bit, let's assume we're using a simple {{{a.atk - b.def}}} formula. When a.atk is 11 and b.def is 10, that's 1 damage. When a.atk is 12, that's 2 damage. Even though the stat changed by a mere 1 point, the damage was doubled. A small change now lets you beat the enemy twice as fast.
With large variance between character's stats, such as you often see in RPGs with specialized roles, this can get out of control really fast. Against an enemy with high defense, weak or even average characters might be nearly useless while strong characters are barely affected.
Subtractive formulas are well-suited to games where stat differences are very important, and strategy will depend heavily on things that affect that, such as equipment or status effects. Players will need to pay close attention to every character's capabilities, and match up strong attackers to strong defenders in order to progress at all.
Subtractive formulas tend to be relatively unintuitive. The same change in stats isn't reflected with the same change in the final result, and players can flounder helplessly against high-defense enemies if they don't know what they're doing. It asks more of your players and requires them to engage deeply with the mechanics and mathematics.
This may be the design philosophy you want; it was when I made <i>Cartoon Battle</i>, which is why this was the formula I used. But what if you want something a bit easier for the player to understand?
In that case, you want <b>divisive defense</b>. This takes the form {{{DMG = a.atk/b.def}}}. Under this system, stat changes are reflected intuitively: doubling Attack will always double your damage, and doubling Defense will always halve your damage.
Like with subtractive formulas, you can combine this with constants and weights, such as:
<div class="formula">DMG = C + D * [(W * a.atk)/b.def]</div>
Though this formula is more intuitive for the player, it's unfortunately trickier to design, and requires additional knowledge of mathematics and algebra. Note that Defense can never be 0 with this formula: if it is, you will get a division by zero error! Similarly, a negative Defense value will just turn damage negative and mess up the whole formula, unlike the intuitive result it generates with subtractive defense. (I recommend thinking of some way to make negative Defense act as a multiplier instead of a divisor if you want to preserve that functionality.) You will need handlers for these special cases, or careful checks to ensure they never appear.
One divisive formula that scales relatively well is:
<div class="formula">DMG = 4 * a.atk ^ 2 / (a.atk + b.def)</div>
As always, you can also combine these principles to create a formula with both a subtractive and divisive term.
A more detailed discussion of the pros and cons can be found on the RPG Maker community forums <a href="https://rpgmaker.net/forums/topics/14545/?post=635978#post635978">here</a>. The example divisive formula is taken from there.
<h3 id="dmgform.special">Special Attacks and Weighting</h3>
The game won't be terribly interesting if all you can use are regular attacks, right? Most RPGs have special skills that do more damage than usual. This means you're going to need different formulas for those.
<h4 id="dmgform.special.1">Lumped Weighting</h4>
The simplest method is to just multiply the whole formula by a weight value, like so:
<div class="formula">DMG = [20 + 0.4 * (a.atk - b.def)] * W</div>
<div class="formula">DMG = (4 * a.atk - 2 * b.def) * W</div>
<i>Bonfire</i> uses this method. This is probably the most intuitive option: if you want a special attack to do more or less damage, it'll be scaled by the exact amount you want in all situations; the skill will behave fundamentally the same as a regular attack, as the scaling factor doesn't interact with any stats directly.
<h4 id="dmgform.special.2">Specific Weighting</h4>
However, predictability can be boring. Maybe you want an attack to behave differently in different sitations. Let's look at what happens if you apply the weight to a term within the equation, rather than over the whole thing:
<div class="formula">DMG = W * a.atk - b.def</div>
Using RPG Maker's formula as an example, let's make a skill that behaves like {{{5 * a.atk - 2 * b.def}}}. With 10 Attack and 10 Defense, that's {{{50 - 20 = 30}}}, a 50% improvement over the basic attack damage of 20. In other words, that's the same effect as if we had applied a weight of 1.5. This remains constant across scale-up, provided there remains no difference between the Attack and Defense values.
However, let's test this with 10 Attack and 5 Defense. The basic attack would do {{{40 - 10 =}}} 30 damage, and the special attack would do {{{50 - 10 =}}} 40 damage. Now the improvement is 33%, not 50%.
What if we go in the opposite direction, 10 Attack vs. 15 Defense? The basic attack does {{{40 - 30 =}}} 10 damage, and the special attack does {{{50 - 30 =}}} 20 damage, a 100% increase!
The effectiveness of special attacks weighted through this method, like everything in a subtractive system, is heavily dependent on the difference in stats. A special attack will yield a greater effect against a tough opponent than a weak one. This isn't very intuitive mathematically, but it is logically: against an armored foe, you can break through using a stronger attack.
This method is good if you want a battle system based around the use of the correct skills. It turns battles into a puzzle of sorts: the same skill has varying usefulness in different situations, and the challenge can be in figuring out which skills to use when. This is the design principle I wanted for <i>Cartoon Battle</i>, which is why I used this method.
(Note that my formula weights the constant and the attack value as a lumped term. I recommend doing this if you use a constant; I initially tried weighting the attack value only, but the constant ended up dominating the equation and making all attacks very similar.)
<h4 id="dmgform.special.3">Adding Constants</h4>
So far I've only mentioned multiplication, but we're forgetting something even simpler: addition! Just add an extra constant to the attack, and it'll be stronger by that exact amount, no extra fuss needed. RPG Maker uses this method for its default magic formula, which looks like this:
<div class="formula">DMG = 150 + 2 * a.mat - 2 * b.mdf</div>
If you use a scaling formula like RPG Maker's, the constant will gradually become less impressive over time as the damage from the typical attack increases. This method is therefore ideal for a skill you only want to be useful for a certain period of time, perhaps to help the player at the beginning before you force them to transition to other tactics.
<h4 id="dmgform.special.4">Skill Progression</h4>
Actually, RPG Maker in particular uses another trick to ensure constant-type skills become obsolete. Let's take a closer look at that formula:
<div class="formula">DMG = 150 + 2 * a.mat - 2 * b.mdf</div>
Notice something different? The constant isn't the only thing that changed: the weight on the attacking stat changed too. Every point of Magic Attack only adds 2 points of damage, while regular attacks get 4 points of damage for every point of Attack.
Let's look at how that scales. With offensive stats at 20 vs. defensive stats at 10:
<div class="formula">PHYSICAL: 80 - 20 = 60</div>
<div class="formula">MAGICAL: 150 + 40 - 20 = 170</div>
Wow -- that's nearly three times as much damage! That magic spell's going to look pretty awesome to a player with those stats. But let's say they gain a few levels, and now it's 65 vs. 55:
<div class="formula">PHYSICAL: 260 - 110 = 150</div>
<div class="formula">MAGICAL: 150 + 130 - 110 = 170</div>
Now it's not looking so great -- still better than a regular attack, but only by a little. Let's level them up some more, so now they're at 100 vs. 90:
<div class="formula">PHYSICAL: 400 - 180 = 220</div>
<div class="formula">MAGICAL: 150 + 200 - 180 = 170</div>
Now the regular attack is actually <i>stronger</i> than the spell! In fact, the spell hasn't improved <i>at all</i> -- it's just as strong as it was at the beginning! What gives?
The key is that the scaling factor was changed, so that Magic Attack was now on equal footing with Magic Defense. In the regular damage formula, more Attack means more damage even if the difference between offense and defense doesn't change. If the same scaling was used, magic would always be that much stronger than the regular attack; but with the scaling factor cut, wizards have to work twice as hard to keep up with fighters.
This may sound terribly unfair, but a lot of RPGs use it. You may have noticed yourself if you've played a lot of RPGs that magic-users seem to get less impressive as time goes on. That's because in most RPGs, you're not intended to use the same spell you get at the beginning all the way through to the end. Instead of getting stronger by improving their stats, wizards are intended to get stronger by getting better spells. Just when the Fire 1 spell is falling behind, give your wizards Fire 2, the same spell but with a bigger constant, and they'll be back on top.
It's a difficult balance, though, and a lot of games miss the mark. You often run into issues at the endgame, where you run out of new skills but keep getting stat boosts. Is the last tier of magic good enough to keep casters competitive? Even if you can keep them on par with fighters, if casters need to spend resources like MP and fighters don't, fighters may still come out on top. But you don't want to overdo it and make the final spells so strong they make stats meaningless, either.
Personally, I'm not a big fan of this method. Just about the only RPG I felt accomplished it adequately was <i><a href="http://site.scfworks.com/?page_id=8" target="_blank">Last Scenario</a></i>, itself an RPG Maker game.
You may notice I didn't bother adding magic stats in the default engine at all. I, personally, don't actually see the point in a split; most RPGs nowadays give special attacks to fighters too, and once you do that there's not really a functional difference. As will be discussed later, I think there are better ways of making mages tactically distinct from fighters.
<h3 id="dmgform.htk">HP and hit-to-kill ratios</h3>
Damage is only one half of the equation. How do you balance HP along with it?
As you approach this question, you may be missing the forest for the trees. The exact numbers don't really matter; they can be basically anything. Observe:
<div style="display:flex; width:auto; align-items:center; justify-content:space-evenly">[img[setup.ImagePath + "documentation/HP_PaperMario.PNG"]][img[setup.ImagePath + "documentation/HP_DragonQuest.PNG"]][img[setup.ImagePath + "documentation/HP_FinalFantasy.PNG"]]</div>
RPGs can have wildly varying HP values. These screenshots (from <i>Paper Mario</i>, <i>Dragon Quest V</i>, and <i>Final Fantasy VII</i>, respectively) show a variance of over a hundredfold across three different examples -- yet all of them are critically acclaimed, well-balanced games. How is this possible, and how should you choose your own HP values?
The answer is to look not at the HP values themselves, but <i>the number of attacks it takes to defeat a character</i>, or <b>hit-to-kill ratio</b>. This is an extension of a general principle of game design: Ratio of action to response. How many times does a player have to perform a certain action before they achieve a desired outcome? This is the truly basic unit of game design, and it is a crucial one. Too low and the game may be too easy, too high and the player might get bored and frustrated by feeling that their actions accomplish nothing.
HP and damage design are therefore two sides of the same coin. Let us revisit our three examples, but look for <i>damage</i> values instead:
<div style="display:flex; width:auto; align-items:center; justify-content:space-evenly">[img[setup.ImagePath + "documentation/DMG_PaperMario.PNG"]][img[setup.ImagePath + "documentation/DMG_DragonQuest.PNG"]][img[setup.ImagePath + "documentation/DMG_FinalFantasy.PNG"]]</div>
That 7000 HP might look like a lot, but at 2800 damage per attack, you'll be down in only three hits! Mario, despite his paltry 20 HP, can actually take an additional hit if the average attack does 5 damage. <i>Dragon Quest</i>'s 88 damage would be overkill against Mario and insignificant against a <i>Final Fantasy VII</i> character, but provides a reasonable hit-to-kill ratio of 4 in its native setting.
This is the measure that you should consider when designing player characters and enemies. I would recommend starting with a general scale for either HP or damage -- what do you want the average attack damage to be, or the average starting HP? -- and then fill the other based on your desired hit-to-kill ratio. <a href="https://rpgmaker.net/forums/topics/11883/?post=414635#post414635" target="_blank">RPG developer Craze recommends a HTK of 8-12 for player characters</a>, with lower numbers obviously resulting in a more defensive/healing-heavy strategy. To make it easier, you may choose to remove the issue of scaling through a damage formula such as <i>Bonfire</i>'s and keep HP constant throughout the game. In <i>Bonfire</i>, for example, all player characters are fixed at 100 HP, with enemies classed into clear "tiers" that each share an HP value.
Notably, I did <i>not</i> do this math for <i>Cartoon Battle</i>, which is what resulted in battles being too slow to begin with. I initially gave characters ten times the HP of <i>Bonfire</i> but only twice the damage, which of course resulted in hit-to-kill ratios that were far too high and just made battles a slog. Learn from my mistake! Plan out how long you want battles to be and how durable you want characters to be, and make sure your numbers align with your desired results.
Note that all of this interacts with <a class="noExternal" href="#abilities.heal">healing</a> and [[stat growth|Additional Features]] as well. You may balance the numbers well for one snapshot of gameplay, but can you maintain it through the whole course of the game?
<h3 id="dmgform.random">Randomness and Variance</h3>
Predictability can be boring. Sometimes, you want a little randomness in attack outcomes.
This isn't really my wheelhouse, but you can read about how to do this in the documentation for JavaScript's {{{Math.random()}}} function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
See also the "Misses and Critical Hits" section of [[Additional Features]].
<h3 id="dmgform.exta">Additional Reading</h3>
It's also possible to use other variables, such as level, as factors in damage formulas. Check out <a href="https://bulbapedia.bulbagarden.net/wiki/Damage#Damage_calculation"><i>Pokemon</i>'s damage formula</a> if you'd like to see one that's really complex! For more resources, an RPG Maker community thread with more technical explanations on how to make more complex formulas can be found <a href="https://rpgmakermv.co/threads/damage-formulas-101-mv-edition.2172/" target="_blank">here</a>.
<h2 id="abilities">Designing Abilities</h2>
An RPG is boring if you can only do one thing. You should think about what options you want to give the player. I'm not just talking about different kinds of damage formulas, anymore. The sky's the limit, here. Does that seem overwhelming? Let's go over some basic design principles to ground you a little.
<h3 id="abilities.basic">Before you begin, what are your regular commands?</h3>
[img[setup.ImagePath + "documentation/command_example.JPG"]]
If you've played any RPG before, chances are you're familiar with having these four options in battle: ATTACK, MAGIC, DEFEND, ITEM. Some games add or subtract a few commands, but this is the basic setup used by RPG Maker, <i>Dragon Quest</i>, <i>Final Fantasy</i>, and many others. The above image is from <i>Dragon Quest VI</i>.
Before we design abilities, we're going to need to break apart this fundamental model -- because, contrary to popular belief, it's not the only one you can use! It's not the model used by the default engine, and I'll explain why.
First, notice how all of these actions have a positive effect. The player will accomplish <i>something</i> during their turn. Even DEFEND, commonly seen as a "skip turn" option, helps protect the character against the next attack.
If you've played <i>Cartoon Battle</i>, I hope you learned not to take that for granted! I replaced it with an action that really does do nothing. This wasn't just because I wanted to make things harder for the player, but because I wanted to turn defending into a special role. Only Fighter can defend themselves and others. By limiting the player's options in this way, I hoped to make them take notice of it, and learn how to use it tactically.
The second thing to notice here is that ATTACK and MAGIC are separate. Why is that, exactly? MAGIC includes the ability to inflict damage. Could we not lump them together? The distinction is typically that attacking is always free while magic requires the use of a resource, but this isn't always the case. There are RPGs that make even regular attacks consume resources, and have special skills that are free.
There are really two main reasons for this distinction, one historical and one practical. Early computer RPGs were inspired by <i>Dungeons & Dragons</i> and similar tabletop RPGs, where not every character had access to magic; under the rules, magic was a solidly distinct action from regular attacks. It's mostly fallen out of favor as a design practice now, but this principle carried over into many early computer RPGs, where certain classes or characters couldn't use magic at all.
The other reason is that the player always needs a means to advance the game, and that means inflicting damage. Characters could run out of spells or have their magic disabled, so splitting ATTACK off entirely was an easy way to ensure the player always, in theory, had a means of completing the battle. (There is also, as touched upon in <a class="noExternal" href="#dmgform.special.4">the damage formula section</a>, the matter of different stats being used for magic; though this distinction has been almost completely lost in modern times, as physical skills are commonplace in RPGs now.)
But oh, why should we be so generous? What if we want to be really mean and give the player no free rides? There's nothing requiring you to make a basic attack a guarantee. You can, if you really want to, force the player to interact with the game through skills alone.
This is what I did in <i>Cartoon Battle</i>: I folded ATTACK into MAGIC to create the ACT command. Every character does still have a "basic attack" of sorts, but by doing it this way I got to personalize it for each character, and give them all subtly different behaviors. I believe this leads to a richer design, with more options for the player and personality for the characters. It also makes it possible for the player to be unable to attack at all if they mismanage their resources, creating a more punishing environment that encourages planning and forethought.
This is key, though, and something to be discussed later: I didn't make actions consume nonrenewable resources. Energy for actions continually regenerates, so even if the player can get themselves into a sticky situation, they can't ever get into a state where the battle is totally unwinnable.
There are cases when it makes sense for ATTACK to be separate, such as if skills consume limited resources and/or players are not meant to use them often. But I encourage you to think about what purpose you want to use it for, and if it's better off folded into general actions.
ITEM could theoretically be folded into ACT too, if we really want to compact this. But generally, there are reasons to keep it separate, as in most RPGs items do behave significantly different than skills. You could always just not feature items at all, too, if they're too much of a hassle.
So: when desigining abilities, first free yourself of assumptions. Think about what your player's most basic options are going to be, and what functionalities you want to make an ability or a regular command.
<h3 id="abilities.resources">Resources and Game Balance</h3>
Typically, there's some reason why players can't just use their best abilities all the time. If they could win every battle by just using the same ability over and over, that wouldn't make for a very interesting game. Most games implement a number of ways of restricting the use of special abilities, often by tying them to some kind of resource. We'll discuss a few of the more common methods below.
<h4 id="abilities.resources.mana">Mana Points (Limited Resource)</h4>
This is probably the one you're most familiar with: special abilities consume a limited resource, typically one that's not easy to restore. This most commonly takes the form of MP, or Mana/Magic/Moxie/whatever Points. Skills consume MP, which can be restored by resting, often available only in certain locations. Like many RPG mechanics, this is a holdover from <i>Dungeons & Dragons</i>, which had an even more literal system: wizards had a fixed number of spells they could cast per day. (The very first <i>Final Fantasy</i> actually used this system! MP did not become commonplace until around the Super Nintendo era.)
The idea behind this system is <i><b>resource management</b></i>. You enter a dungeon with a certain number of spells, and you have to figure out how to use them appropriately as you progress. You could use up all your MP to quickly dispatch your first encounter, but that could leave you helpless against the next one. The player is encouraged to think ahead and use their skills efficiently so as not to waste MP.
Unfortunately, this design principle has been rather forgotten in modern times, to the point that later games like <i>Final Fantasy XIII</i> eschew MP entirely. Most RPGs are now designed as a series of tactical set piece battles, with regular encounters being little more than filler before the boss fight. You know that full heal you always get right before the boss? You're not supposed to get that! <i>Dungeons & Dragons</i> and the early <i>Final Fantasies</i> threw you into boss fights in whatever state the dungeon had left you, which meant every battle mattered in terms of managing your resources. To make matters worse, most RPGs have items that can restore MP, so even if you do run out all you need to do is pop one of your zillion Ethers and you can go right back to slinging spells. This all makes MP virtually meaningless: you're free to trivialize normal fights with strong skills, and even in long battles where you may run out of MP, it'll only take one turn for you to get back on track.
Now, there's nothing wrong with focusing on tactical set pieces over strategical resource management. Some games eliminate resource management entirely by making sure you go into every battle at full strength. However, you should consider that if you do want your game to be more tactical, MP might not be the best system to use.
In sum: MP is best for games based on strategic, long-term resource management. To get the most out of it, you probably shouldn't give players enough that they can use skills all the time, or make it easy to restore. Perhaps consider removing the possibility of in-battle MP restores?
Implementing this system is quite easy: just add an {{{mp}}} attribute to the "Actor" or "Puppet" class in the story JavaScript, or use the current {{{en}}} attribute and change the battle coding to make it behave like a limited resource.
Some examples of games that use this system well (external links):
<ul>
<li><i><a href="https://rpgmaker.net/games/5751/" target="_blank">Soul Sunder</a></i>, a survival horror RPG where even regular attacks consume MP</li>
<li><i><a href="https://rpgmaker.net/games/5904/" target="_blank">Czarina Must Die</a></i>, which has no in-battle restores</li>
<li><i><a href="https://en.wikipedia.org/wiki/Shin_Megami_Tensei:_Devil_Survivor" target="_blank">Shin Megami Tensei: Devil Survivor</a></i>, which gives players very limited MP but makes magic an integral part of the battle system</li>
<li>Though it's not an RPG, the first <i><a href="https://en.wikipedia.org/wiki/Resident_Evil_(1996_video_game)" target="_blank">Resident Evil</a></i> and other survival horror games can give you a good idea of what resource management gameplay looks like.</li>
<li>It may also be instructive to check out the core rulebooks for different editions of <i><a href="https://en.wikipedia.org/wiki/Editions_of_Dungeons_%26_Dragons" target="_blank">Dungeons & Dragons</a></i>. Later editions are often accused of being too much like video games, which is actually good for your purposes! However, the earlier editions (1-3) are the ones with greater focus on resource management and long-term strategy. 1st edition <i>D&D</i> was also the primary inspiration for the original jRPGs <i>Dragon Quest</i> and <i>Final Fantasy</i>, so many design philosophies that are now baked into the genre have their start there.</li>
</ul>
<h4 id="abilities.resources.sta">Stamina (Renewable Resource)</h4>
Some RPGs that prefer a more tactical focus use this system instead: a more limited, controllable resource that changes as the battle progresses.
The simplest form is a resource that regenerates automatically over time. <i>Cartoon Battle</i> uses this system: you gain enough Energy points to use a basic attack every round, but to use a more advanced ability you will need to save up. This system is more intuitive and elastic than MP; it is easier to plan out how to allocate your resources, and you're never trapped in a situation where you're simply out of options, at least not for long.
There are variations within this system that can have significant effects. How many points to you give the player to start with, and how many do you give them per turn? If you top everyone off before a battle but make them regenerate slowly, you can create a similar system to MP or <a href="abilities.resources.cd">cooldown</a> where players can use strong abilities early but have to wait a while before they can use them again. What is the maximum amount they can hold? A low maximum encourages players to use their points quickly to avoid wasting regeneration, while a high maximum may encourage hoarding and more conservative play. Maybe you don't want a maximum at all? Does everyone operate on the same rules, or does everyone have a different flow? I encourage you to play around with these parameters and see how they change the feel of the game.
Alternatively, you can make players work for their energy generation. Often, this is a very good way to encourage players to play the game the way you want them to. If you want battles to be fast-paced and reward offense, make regular attacks generate energy and players will be rewarded for playing that way. If you want a more cerebral experience, maybe defending or smart play, such as hitting weaknesses, will do it.
You can complicate this even further by adding multiple resource pools and making them interact! Maybe you have traditional MP, regenerating stamina, and manually generated energy points all at the same time. Maybe you can expend one pool to refresh another? It'll be complicated, but it can lead to great fun and tactical depth if you can pull it off.
Some games also make energy a group resource, rather than each character having their own separate pool. This can create more tactical depth, with certain characters who can act as "batteries" for the whole group, but asks a lot more of the player -- if they don't know what they're doing, they can incapacitate the whole group, not just a single character!
In sum: Renewable resources are best for games with a focus on intense individual fights. This system tends to be more intuitive for the player and easier for the designer to balance, so I recommend starting with it. It's very modular, and you can adjust a lot of the variables to create a different experience.
Some examples of games that use this system well (external links):
<ul>
<li><i><a href="https://rpgmaker.net/games/272/" target="_blank">Alter A.I.L.A. Genesis</a></i>, which was a major inspiration for <i>Cartoon Battle</i>; you will notice a lot of similarities. It's much more complicated, though, with a manual generation pool on top of renewable energy, and special equipment that can change the rules.</li>
<li>"Limit Break" abilities, popularized by <i><a href="https://en.wikipedia.org/wiki/Final_Fantasy_VII" target="_blank">Final Fantasy VII</a></i>, are an example of abilities tied to a manually-generated resource</li>
<li><i><a href="https://rpgmaker.net/games/4526/" target="_blank">Wine & Roses</a></i></li>
<li><i><a href="https://en.wikipedia.org/wiki/Steven_Universe:_Attack_the_Light!" target="_blank">Steven Universe: Attack the Light</a></i> is an example of a group resource for actions</li>
<li><i><a href="https://rpgmaker.net/games/3937/" target="_blank">Obelisk: Devilkiller</a></i> features both group and individual resources</li>
<li><i><a href="https://rpgmaker.net/games/7969/" target="_blank">Prayer of the Faithless</a></i> is an interesting example of a game that uses this system and still manages to be resource management. The developer's blog posts are also very instructive in terms of game design.</li>
</ul>
<h3 id="abilities.resources.cd">Cooldown</h3>
Cooldown is a feature that makes the player wait a certain number of turns before they can use a skill again. Cooldown may be tied to skill use itself, or more commonly, each individual skill has its own cooldown.
This is a much more literalized method of skill throttling: if you only want players to use a skill every X turns, you can just make it so, with no further balance testing needed. This has similar advantages to a renewable-resource system, in that it is intuitive for the player and easier to design. It provides similar tactical depth by forcing the player to think ahead so as not to box themselves into a corner.
This method may be combined with any of the others. In particular, it's a good way to mitigate the problems of a limited-resource system in a tactical setting, by preventing players from just using their strongest attacks constantly. You can include other twists too, like things that add or reduce cooldowns.
Personally, I do not like this system; I feel it is too literal and restrictive, and I feel obligated to use high-cooldown skills as often as possible. I prefer the flexibility of a renewable-resource system. However, this system is gaining popularity, and you and your players may feel differently!
In sum: Cooldown has many of the same advantages as a renewable-resource system, but it is simpler and more straightforward, with the pros and cons that implies. It is recommended for novice developers, as it is easy to implement and balance. It is highly compatible with other systems if you want to add more complexity.
This system is already implemented in the default system for enemies. To implement this for the player, you can add the same attribute to the "Puppet" class or to individual actions.
Some examples of games that use this system well (external links):
<ul>
<li><i><a href="https://store.steampowered.com/app/706560/Jimmy_and_the_Pulsating_Mass/" target="_blank">Jimmy and the Pulsating Mass</a></i> (warning: horror game) combines this with a limited-resource system</li>
<li><i><a href="https://rpgmaker.net/games/3937/" target="_blank">Obelisk: Devilkiller</a></i> combines this with a renewable-resource system</li>
</ul>
<h3 id="abilities.resources.recoil">Recoil/Blowback</h3>
Instead of tying skills to a specific resource, you can also limit them by having them affect <i>other</i> resources, such as a character's health and stats. Maybe a reckless attack leaves a character injured, or using dark magic saps their strength. This can create a similar situation to limited-resource systems where the player can go into a fight guns blazing, but recoil is far more punishing of reckless behavior. Exhausting your resources in this system doesn't just lock off your abilities, it can make your characters completely helpless.
Done well, this system can create probably the most tactical and strategical depth of all these systems, forcing the player to make tradeoffs on multiple axes with every action. However, it is very difficult to implement and balance correctly. You have to keep track of not just one resource but several, and ones that interact with the rest of the battle system as well.
As always, you can merge this with other systems as well, possibly only implementing it for a few skills. I did this in <i>Cartoon Battle</i>, where Mage's Sacrifice skill consumes HP, and Fighter's Bull Rush skill self-inflicts a negative effect.
In sum: This system can work with both tactical games and resource management games, but is very complicated. It will take the player some getting used to, and it will be even harder to design fairly. If you want to use this, try easing your way into it by merging it with other systems.
This is the most difficult system to implement: unless you want a consistent theme like all skills consuming HP, you'll have to incorporate unique behavior into every skill.
Some examples of games that use this system well (external links):
<ul>
<li><i><a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon_(video_game_series)" target="_blank">Pokemon</a></i> has moves that inflict recoil damage, and most of the strongest attacks self-inflict debuffs to discourage continuous use</li>
<li>Physical skills in the <i><a href="https://en.wikipedia.org/wiki/Shin_Megami_Tensei" target="_blank">Shin Megami Tensei</a></i> series consume HP rather than MP</li>
<li><i><a href="https://rpgmaker.net/games/7969/" target="_blank">Prayer of the Faithless</a></i> combines this with a renewable resource system, as your stamina is also your defense</li>
<li><i><a href="https://en.wikipedia.org/wiki/Live_A_Live" target="_blank">Live-A-Live</a></i> is a rare example of a version that uses no other skill limitations</li>
</ul>
<h3 id="abilities.elements">Elemental Effects</h3>
If you've played any RPG, chances are you've run into elemental type matchups. You get a bonus if you use fire magic on the ice monster and ice magic on the fire monster, that sort of thing. Sometimes you'll see different flavors, like crushing attacks being more effective against armored foes or a hunter character getting a bonus against certain types of monsters. This is a nice way to diversify your player's options, instead of just giving them one skill that does more damage in all situations.
There's a reason I didn't include this in <i>Cartoon Battle</i>, though: I personally don't feel like it adds very much. In most RPGs, there is no difference in each elemental spell other than the element, so whether you're attacking with Fireball or Ice Bolt creates no change in tactics; you're still just using "the attack that does more damage". If you plan to use elemental effects, I encourage you to add more personality to each one. Maybe fire spells are the only ones that can hit multiple targets at once? Maybe lightning spells are stronger, but cost more? Alternatively, you can go in the reverse direction, and give elements to personalities. If you restrict certain elements to specific characters, you can use elemental affinities to create tactical depth. Say the healer is the only character who can use the element a certain enemy is weak to? That's going to force you to make tactical trade-offs, and fight differently than you would otherwise.
And this is more a writing tip than a game design one, but I encourage you to break out of the standard mold of the Classical elements or the fire/ice/lightning trifecta. These are <i>elements</i>; fundamental components of your world and everything in it. They should inform the story and atmosphere of your game. <i>Why</i> is everything made of fire, water, wind, and earth? What does that <i>mean?</i> Are your different elemental forms for monsters functionally identical except for the different hat? Why on Earth are you doing that when you could be making enemies that are actually meaningfully diverse and tell the player something interesting about your setting?
If you're making something a core gameplay feature, it should tie into your story too. I purposefully encourage this with the vagueness of the default "elements" in the engine -- it's easy to map the colors onto the standard fire/ice/lightning/dark/holy set, but as I say in the Artist's attack descriptions, you can interpret them many different ways. Mabel isn't weak to Blue because she's bad with cold; she's weak to Blue because she's bad at dealing with heavy and negative emotions, which is what I choose to interpret Blue as representing. Think hard about what elements you choose to use in your game and how you can use them to inform the story and characters, not just add depth to the gameplay.
<ul>
<li>In <i><a href="https://rpgmaker.net/games/3554/" target="_blank">Star-Stealing Prince</a></i>, every character uses a different element. Each character has a distinct role, so enemies' weaknesses change the overall group synergy by encouraging different characters towards offense.</li>
<li><i><a href="https://rpgmaker.net/games/3937/" target="_blank">Obelisk: Devilkiller</a></i> may be an example of going too far: it's widely criticized for the major power gap between Solar and Lunar attacks, making Solar-resistant enemies take far longer to beat for no real purpose.</li>
<li>The <i><a href="https://en.wikipedia.org/wiki/Shin_Megami_Tensei" target="_blank">Shin Megami Tensei</a></i> series strongly rewards correct elemental matchups with its mechanics, and encourages a diverse loadout due to limited skill slots and enemies with diverse resistances, including to physical attacks.</li>
<li>Though it's not notable from a gameplay perspective, <i>OFF</i> features some truly bizarre element choices: Smoke, meat, metal, and plastic. This supports the game's overall surrealist atmosphere and the strangeness of its world.</li>
</ul>
<h3 id="abilities.heal">Healing</h3>
So far we've mainly talked about offensive abilities, but what about healing? If you've played RPGs, you're probably familiar with certain characters having this role; the cleric in <i>Dungeons & Dragons</i>, the white mage in <i>Final Fantasy</i>, and so on.
You may be surprised to learn this is actually a controversial topic in the indie designer scene! There's a school of thought positing that healing skills rob the game of tactical depth, because they prevent actions from having permanent consequences. If the player can just keep healing indefinitely until they win, this encourages players to fall into a rut of "turtling", or playing very defensively and only chipping away at enemies. This can make battles very boring. On the flipside, there exists a tipping point where players will no longer be able to outheal an enemy's attacks, but it may not be clear to the player where this is, causing them to circle a drain of spending all their time healing and never having the breathing room to attack.
However, giving the player a chance to bounce back from their mistakes may be a good thing. If you give players a lot of HP but no healing, they may be doomed by early mistakes but not realize it until the end of the battle; they must manage their resources carefully every step of the way. It can be asking a lot of your players to make them think that far ahead!
A middle ground exists with revival skills. If there is no possibility of in-battle revival, players can recover from some of their mistakes but not all: if they mess up too badly and let characters get KO'd, they're punished with an inability to reverse it. If you still find that too punishing, revival skills themselves are separate balance. Do you want HP hitting 0 to be a meaningfully distinct scenario from HP hitting 1? If so, you probably shouldn't let revival skills recover as much as healing skills. If it's possible to revive a character to full health every turn, then death hath lost its sting, no? You may as well let normal healing skills revive people -- and that is, itself, a valid option used by some RPGs.
It must also be said: there is some visceral satisfaction in seeing a strong attack bring your party to its knees, even if intellectually you know you can reverse it on the next turn. Your game will lack this if the enemies are just chipping away at you at the same rate you're chipping away at them. An enemy ripping off 20% of your HP in a single attack can be even more significant than ripping off 80% if you can't get it back, but our gut reaction is still that it's less impressive.
Healing skills are a tricky balance to get right. Experiment with it, and see what other games have done. It's probably a better idea to tie healing to limited resources to avoid an endless turtling scenario, but it can work with renewable resources as well.
These games may be worth consideration (external links):
<ul>
<li><i><a href="https://rpgmaker.net/games/3937/" target="_blank">Obelisk: Devilkiller</a></i> combines healing with long cooldowns with the end result of slowing the pace of damage, but not actually reversing it</li>
<li><i><a href="https://store.steampowered.com/app/706560/Jimmy_and_the_Pulsating_Mass/" target="_blank">Jimmy and the Pulsating Mass</a></i> makes all healing skills proportional to max HP, so they are just as effective throughout the game</li>
<li><i><a href="https://rpgmaker.net/games/5751/" target="_blank">Soul Sunder</a></i> limits healing to items</li>
<li><i><a href="https://rpgmaker.net/games/3135/" target="_blank">Edifice</a></i> takes no healing to an extreme: there is no healing <i>outside</i> of battle, either</li>
</ul>
<h2 id="items">Item Design</h2>
Most RPGs feature items the player can accumulate over the course of the game. They often expand the player's options in some way by giving them additional resources.
Items can usually be classed into two categories: <b>Consumable</b> items are one-use items that produce immediate effects and disappear after use. Health potions and spell scrolls fall into this category. <b>Equipment</b> can be worn by characters to improve, or at least change, their capabilities. A steel sword, leather armor, and a ring of +2 Strength are all examples of this category.
<h3 id="items.consumable">Consumables</h3>
Consumable items can be thought of as a form of <a class="noExternal" href="#abilities.mana">limited-resource abilities</a>, and similar design principles apply. The main distinction is that their effects are (usually) independent of the characters' stats, so everyone can use items equally well and there are no soft limits on how many you can store at once. (But not always! Some systems allow different characters to carry more items, or increase the amount of items you can hold over the course of the game. It's also possible for items to mimic abilities complete with drawing on the relevant stats, or using their own stat entirely. <i>Cartoon Battle</i> does the latter.)
The important considerations here are how powerful you allow items to be (more powerful than regular abilities? Less?), how many you give the player (can everything be bought, or do you only get a fixed amount for the whole game and that's it?), and how many the player can store at once (can you only hold a certain number of different items? Can they stack?). If the player can go into battle with 99 full-heal potions in their back pocket, there's basically no way they can really lose. Some people enjoy the puzzle of managing limited inventory space, but some people find it a frustrating waste of time. And if items have uniquely powerful effects, that will skew gameplay in favor of item use; while if items are inferior to standard abilities, they may become irrelevant.
A major problem that tends to crop up with consumable items is <b>hoarding</b>. Even if you do run into a situation where you could really use that full-heal elixir, <i>what if I need it later?</i> Cue walking into the final boss fight with 99 elixirs and turning it into a cakewalk. You should try to this problem. There are a number of ways you can do so, but probably the easiest way is to set low caps for items. <i>Final Fantasy</i> and most RPG Maker systems let you store a dizzying 99 of each item, which is probably more than you can ever use. If you cap it at a much lower number, that will teach the player that they are supposed to be burning through their inventory space to make room for new items instead of hoarding.
A low item cap also helps with game balance, as it restricts the number of states a player can enter battles with. A player who enters a battle with 99 healing potions is going to have a much easier time than one who enters with 10 or none at all; it's really hard to balance that to give a challenging but surmountable experience to every player. A lower item cap means a smaller range of preparedness you need to account for. By default, the stock cap in Another RPG Engine is a relatively low <b>9</b>, but you can change it by editing the {{{ITEM_MAX}}} variable. You can even make stock caps unique for every item, such as allowing fewer copies of higher-level items compared to lower ones; some entries in the <i>Shin Megami Tensei</i> series do this.
It also helps to clearly communicate item scarcity to the player. A lot of items are hoarded because the player doesn't know when they'll get another one, if at all. Something like a rarity marker on items can help the player make more informed decisions. Or, even more directly, just pull back the curtain and show them the locations and drop rates of items in the world. (See the Bestiary in [[Additional Features]] for one way to implement this feature.)
You can also just eschew items entirely, of course, if you'd rather keep things simple.
For more on this topic, see the video "<a href="https://www.youtube.com/watch?v=HT-Z03YVBPI&list=PL8K0_g1wdQepKF0a8eh_4dhhivcSrSKyO&index=19" target="_blank">How Can I Stop Item Hoarding in Games?</a>" by Design Doc.
These games may be worth consideration (external links):
<ul>
<li><i><a href="http://site.scfworks.com/?page_id=8" target="_blank">Last Scenario</a></i> has its attack items correspond to spells, but you often get high-level attack items long before you get the corresponding spell, making them function as previews of later abilities. The intense difficulty of the boss fights also requires you to use all your resources to the fullest, including items.</li>
<li>In <i><a href="https://store.steampowered.com/app/706560/Jimmy_and_the_Pulsating_Mass/" target="_blank">Jimmy and the Pulsating Mass</a></i>, consumable items are almost never found outside of shops, and you can only carry 9 at a time. This means you will never take them for granted; they are a purposeful investment, and you cannot trivialize battles by stocking up on dozens of everything.</li>
<li><i><a href="https://rpgmaker.net/games/5751/" target="_blank">Soul Sunder</a></i> and its spiritual successor <i><a href="https://rpgmaker.net/games/7969/" target="_blank">Prayer of the Faithless</a></i> have largely item-based battle systems</li>
</ul>
<h3 id="items.equipment">Equipment</h3>
Equipment is a common feature in RPGs. Over the course of the game you obtain different weapons, armor, and other trinkets that improve your stats or confer other advantages. This adds tactical depth to the gameplay, as it provides a resource you can use to modify your characters, thus creating different set-ups for battle.
The most common way this manifests in RPGs is that equipment just provides better bonuses as the game goes on; your starting sword gives +1 Attack, the sword in the next town gives +3 Attack, the sword after that gives +5, etc. This creates a sort of secondary progression method alongside experience levels (discussed in [[Additional Features]]). Even if you have good base stats from leveling up, you will fall behind unless you update your equipment too.
I would generally recommend avoiding this model. I find it redundant with normal level up mechanics, and it provides no tactical depth: if every next item is objectively better than the last, you never have reason to do anything differently. You'll just add another thing you'll have to balance for, for no real benefit.
A better method is to think of equipment as providing <i>qualitative</i> changes rather than quantitative ones. Maybe a weapon inflicts a status effect, or deals extra damage to a specific type of creature. Maybe a piece of armor makes you take more physical damage, but grants you immunity to status effects. Maybe an accessory changes a healing spell into a destructive one, allowing your healer to play a different role. Ideally, the equipment you have at the start of the game should still be viable at the end, or at least most of the way through. This encourages the player to make evaluations and trade-offs over the course of the game instead of finding the one equipment loadout that's best in all situations. You can still have a "progression" of better equipment, generally by combining multiple effects, but the progression will be less uniform and absolute.
This is also not in any way a required feature for an RPG; don't feel bad if you don't want to use it.
<h2 id="effects">Status Effects</h2>
Status effects are things that modify the normal flow of battle. They may change stats, lock off certain abilities, or incapacitate characters entirely. They can affect the whole field, too, not just individuals! This is a great, and often crucial, way to add tactical depth to an RPG. Without status effects, turn-based RPGs are just damage-race number games, which is really boring! Status effects allow you to make strategical trade-offs and throw a wrench into established strategies, forcing the player to adapt and improvise. However, status effects are also one of the things players most often complain about. Careful planning and balance testing is necessary here.
For further reading, see the video <a href="https://www.youtube.com/watch?v=ThDVGP4UB30&list=PL8K0_g1wdQepKF0a8eh_4dhhivcSrSKyO&index=18" target="_blank">What's the Point of Status Effects?</a> by Design Doc.
<h3 id="effects.control">Hold Effects</h3>
Hold effects restrict what characters can do in battle. The one you're probably most familiar with is the "Silence" effect, which restricts the use of magic in several of the most popular jRPG franchises. The excellent indie horror RPG <i>Soul Sunder</i> takes a more visceral approach, with "arm injuries" locking physical techniques and "head injuries" locking magic. I would also classify effects that prevent you from acting at all under this label.
Hold effects are usually binary in their application -- you're either affected or you're not. This makes them very easy to design and implement.
However, balance is, as always, a concern. The power of these effects will depend a lot on the rest of the battle system. Is magic really crucial? Then locking that is going to incapacitate the player pretty badly. Can magic only be used by one character? Then Silence won't even matter to anyone but them! And magic's not the only thing you can lock -- you can disable items or even regular attacks.
This also interacts interestingly with status cures. If Silence locks spells, you can't use your status cure on yourself to fix it! This tends to lead to Silence cure items having higher value than others -- but you can always turn it around with an item-sealing Embargo effect that can only be cured by magic! Some designers, often indie ones who are likely just as sick of this as their players, will go easy on you and let status cure spells remain usable under skill locks. In <i>Cartoon Battle</i>, I took this route with Fighter's self-curing Meditate ability and Witch's Cleanse, but not with Mage's Restoration spell.
If you don't make cures easy, though, you have to be careful with how often you throw these out, especially total paralysis effects. If an enemy is paralyzing the whole party with every attack, the player can feel frustrated and powerless, forced to waste time watching their characters slowly die with nothing they can do about it. And if you include a paralysis effect that doesn't wear off naturally, that's effectively an instant kill attack. If only certain characters can cure it and they get hit, they're disabled for the rest of the fight. (If you played <i>Cartoon Battle</i>, you probably experienced this in the Bill Cipher fight, where petrification could only be cured by limited items if it struck your healers, Witch and Mage.) Maybe you want to be that hardcore, but if you don't, maybe pull it back a little.
One good way to keep hold effects manageable is to provide characters with a protection effect when they wear off, preventing them from being "stunlocked" continuously. I used this with <i>Cartoon Battle</i>'s "Alert" status, which is automatically applied after a stun and prevents future applications of "Stunned" until it wears off. I took this idea from <i>Jimmy and the Pulsating Mass</i>, which additionally makes the "Alert" status last longer after every stun, further discouraging its overuse.
<h3 id="effects.loss">Loss-of-control / Uncontrollable Effects</h3>
Loss-of-control effects are similar to hold effects, but different in their execution. They remove a player character from your control, and force them to act under an AI routine instead. Examples you may be familiar with are the "Berserk" and "Confusion" effects common to many RPGs.
While these effects don't prevent a character from acting <i>entirely</i>, they can still severely limit their abilities. Usually, these effects restrict a character to using only their basic attack ability, and you have no say in who they target, preventing you from effectively coordinating attacks. These effects may also compel the character to attack their <i>allies</i>, which is even worse than just preventing them from acting at all!
The default engine has support for these effects, but only in their most basic form: The affected character only uses their basic attack, and selects their target completely randomly. It is possible to generate a more sophisticated AI that can choose from multiple abilities -- for instance, maybe the character always chooses their strongest attacking ability with no regard for the cost, quickly exhausting their resources? But if you need to make unique logic for every character, that's going to be a lot of work. You can also implement more complex targeting logic, like the standard targeting logic for enemies.
Some games have these effects convey a tradeoff, providing additional advantages in exchange for the loss of control. For instance, the "Berserk" status provides an offense boost in some games. (In others, it doesn't -- it's just loss of control.) They also have interesting interactions with damage reflection, counters, and other features that require careful and judicious targeting. Under normal circumstances you may not find damage reflection too concerning, since you can just stop attacking if you get too hurt... but if the enemy inflicts a berserk effect, your characters will keep attacking with no regard for their own safety! These effects also have interesting combinations with other effects, such as debuffing a character's Attack before berserking them, or <i>buffing</i> their Attack before turning them on their allies. Get creative.
It should be noted that these <i>are</i> effectively hold effects, and the same cautionary notes apply. Don't use them too much, or players will feel powerless and frustrated.
<h3 id="effects.statmod">Stat Modification Effects</h3>
Stat mods are effects that temporarily modify your stats. They can often function as a "soft" version of hold effects: it may not prevent you from using magic outright, but if your magic stat has been crippled to the point that you'll only do middling damage... maybe best to wait. They can also incapacitate character archetypes that rely heavily on certain stats: the tank, for instance, might not be up for provoking attacks if their defenses have suddenly been stripped.
These effects are simple in theory, but they can really throw a wrench into players' strategies and shake up complacent tacticians.
But... this is going to be a numerical effect, so fine-tuning is crucial. Just how much are the stats changed by? Do you want it to be possible to drain a character all the way to 0 in a stat? Into the negatives?
The answer is first going to depend on what the stats actually <i>do</i>. How does a stat correlate to its effect? Will you only need small changes to see significant results, or big ones? If stats are relatively small and even single-digit changes can be significant, stat mods can get pretty overpowered if you're not careful.
Once you know what range of stat changes you want to be working with, there are a few ways you can get there. Most RPGs take a simple approach: effects always change stats by the same proportion, say 50%. This is easy to test because it means the effect always behaves the same no matter who uses or receives it.
However, complications still arise from this method. Recall from our discussion of <a class="noExternal" href="#dmgform.defenses">subtractive vs. divisive damage formulas</a> that stat changes can have wildly different effects depending on how they're applied. In a divisive defense system, fixed-proportion changes will work great throughout the whole game: the stat's effectiveness will always be reflected by the same amount. However, in a subtractive defense system, it's <i>absolute</i> changes that matter. If your characters' stats are increasing over the course of the game, the same proportional changes will lead to greater absolute differences. You can balance it well for the beginning of the game, but it'll behave totally differently by the end! Changing the effect to an absolute modification can be a viable solution, depending on how the rest of the system works.
If you want to make things more complicated, you can actually make stat mods function like an attack, with variable effects depending on the user and target. <i>Cartoon Battle</i> does this: stat mods use the same basic formula as regular attacks, but with the base and damping constants halved. This is very tricky to balance! Even if you can balance it correctly in a resting state, results can be volatile if the governing stats change. In the current version of <i>Cartoon Battle</i>, even a low-power Curse can totally cripple an opponent if their Special has been reduced. So maybe don't use this method for your first rodeo. Done well, however, it can create a cool shadow-battle mechanic where effect specialists can chip away at each others' stats just as fighters chip away at their HP.
The holy grail of this method is probably to make effect damage use the exact same formula as damage. <i>Bonfire</i> manages to do this, thanks to its heavy stat damper that both reduces the effect of large stat changes and prevents a buffed character's effects from getting too powerful.
<h3 id="effects.dot">Damage Over Time</h3>
Damage-over-time effects inflict damage at certain intervals, typically the start or end of a character's turn. This is one of the simplest effects conceptually, but one of the most complex to actually design.
The first question you want to ask is: how much damage? There are a lot of methods you can use, discussed in [[the code section|Documentation]].
Most RPGs use <b>proportional</b> damage, meaning that the effect inflicts a portion of max HP (usually 1/8th). This kind of damage affects everyone equally: everyone will go down in the same number of turns, no matter how much HP they have. This levels the playing field between tough and fragile characters, because you can't mitigate damage-over-time with more HP like you can with regular attacks. This may be a problem if you want a game with a heavy tanking and defensive focus where that distinction is supposed to matter; or you could purposefully use it as a special case to turn those rules on their heads.
Other games, including <i>Cartoon Battle</i>, calculate damage-over-time like a regular attack, allowing it to be mitigated by higher HP and defenses. You can make this calculation similar to normal damage, thus making damage-over-time as dangerous as a regular attack, or you could shrink it, making damage-over-time less significant. This may seem redundant with a regular attack that just does more damage, but you could use it as a purposeful tradeoff: it will inflict more damage overall, but you have to wait while the enemy continues to attack, and it could be cut off early if the target has a curing ability. You can also make it draw from a different stat, creating a mage/fighter distinction where certain characters are better at dealing damage through different methods.
The second question is: how long does it last? If it only lasts a set number of turns before expiring, then it is effectively just a delayed attack. If it never wears off, its total damage becomes indefinite, potentially making it much more dangerous. The latter implies a heavily status-based battle system, where status cures need to be thrown around a lot.
This all interacts with <a class="noExternal" href="#heal">healing</a>, too. You'll need to do a lot of mathematical planning and balance testing to make sure you don't make damage-over-time effects overpowering or insignificant.
<h3 id="effects.death">Instant Death Effects</h3>
Don't use these unless you really know what you're doing. Please. I'm begging you. The entire point of RPGs is that different characters are good at different things. If everyone gets killed equally well, defense-focused characters get the shaft. These totally wreck the game's balance unless you design the whole system around them.
And oh lordy, do not even get me started on <i>random chance</i> instant death effects. Are you trying to make a slot machine? No? Then please don't. Please.
<h3 id="effects.protect">Effect Protection</h3>
Status effects can be big gamechangers, to the point that they may be hard to balance if they're applied every time they're used. You could balance this by tweaking their effectiveness or the battle system around them, but there's often not a lot of middle ground between "harmless" and "overwhelming".
This is why you see a lot of RPGs make attacks apply effects only some of the time, putting it down to random chance. I, personally, hate this. A little random variance in damage or targeting is fine, but when something as important as a status effect is left up to random chance, the battle can go completely differently just based on a coin flip, through no fault of the player. There is something to be said for this method: a totally deterministic game is solvable, and therefore loses the fun if a player figures it out too easily. Randomness keeps a game fresh by forcing the player to roll with the punches and adapt on the fly.
But let's say you don't want that. You can also throttle status effect application through factors under the player's control, such as protective equipment or "safeguarding" effects like <i>Cartoon Battle</i>'s "Chi Shield". I recommend making these all-or-nothing, even if you incorporate randomness in normal effect application: a resource expenditure like equipment or a spell is so major that it's rather unfair if it has a chance of failure.
But then, what if you want some middle ground between complete immunity and complete vulnerablility to a status effect? Most RPGs handle this by making status effect resistance a kind of "dodge" stat, where e.g. a 50% resistance will let you avoid the effect 50% of the time.
I designed a deterministic form of this method in <i>Cartoon Battle</i>. The premise is to flatten random resistance chances into a deterministic pattern. For example: if you resist a status effect 50% of the time, then, on average, you should suffer the status effect on every other application. You can mimic this deterministically by giving characters a statistic (let's call it "tolerance") that acts sort of like "hit points" for the effect application: every time an effect would be applied, it's reduced, and when it runs out the effect sticks.
As an example, if we were to give a character a tolerance of 1 against the "Curse" effect, then if Mage targeted them with Curse, the target would not suffer the effect but their tolerance would be reduced by 1. The next time Mage used Curse, it would stick, as the target's tolerance is now at 0. When the Curse effect wears off, the enemy's tolerance is renewed at 1 again.
The end result of this is that they will <i>always</i> block the effect the first time it's used, but they will <i>always</i> take the effect if it's used a second time. The cycle then repeats. This has the same result as a 50% random resistance on average, but is fully deterministic. A tolerance value of 2 would correspond to a 67% resistance, a tolerance value of 3 would correspond to a 75% resistance, and so on.
I believe this has an advantage in that the player can see a consistent result to their actions. Attempting to apply a status effect to an enemy with random resistance can feel tedious and demoralizing, as there's no telling when or if you'll succeed. This "tolerance" system guarantees a result to a player after a given amount of investment.
There are also additional wrinkles you can add to this system. Perhaps some tolerances do <i>not</i> reset once a status effect gets through, but they have a greater starting value? Or perhaps the reverse, a tolerance that only comes into play after the effect has been applied once? You could create an additional "shadow economy" of attacks that destroy or shore up tolerances without applying effects themselves.
<h3 id="effects.player">Can players use them?</h3>
It's something of a debate whether or not to let players use status effects on enemies. Early RPGs typically didn't allow this at all, and even when they did it was usually a poorly-balanced afterthought.
However, there is good reason for this. All the difficult balance tweaking I mentioned in the earlier parts? If you want to give status effects to players, you have to do that a second time for everything, often running into brand-new problems because enemies usually behave differently than player characters. Enemies typically don't have healing skills, for instance. A proportional damage-over-time effect that's only chipping away at a player who can easily heal it back becomes devastating against an enemy that can't heal. If you apply proportional damage, you can just sit back and win any fight in a few turns. And what about control effects? Enemies typically have fewer abilities and more rigid behaviors; even a partial control effect like a skill lock could effectively be incapacitating. To say nothing of outright stun effects: if you just keep using that, you can win without letting the enemy get a single turn!
I advise giving status effects to your players because it opens up a huge avenue of new tactical options when done well. However, if you don't want to go through the work of balancing it, it's fine to keep things simple and just let the player focus on normal skills.
In <i>Cartoon Battle</i>, I gave players access to most status effects, but not all. Barring the "Winded" status was obvious, because enemies don't use the energy system in the first place. However, I also barred "Dizzy", the skill lock effect, due to the problem mentioned above. How would I even implement that? How would I determine which skills would be affected? How would I lock them off -- would I need to make two behavior sets for every enemy depending on if they were dizzy? That sounded like way more work than it was worth, so I just decided not to bother with it.
Striking this balance is a lot easier if you use a system where players and enemies are all on equal footing, such as in <i>Pokemon</i>. If not, the same effect can have different results when applied to players versus enemies, and some effects might just not be feasible. Do try not to fall into the trap of giving players status effects but making everything they might feasibly be useful against immune to them, though -- that's just pointless.
<h3 id="effects.extra">Noteworthy Examples</h3>
<ul><li>"Monster collecting games", of which <i><a href="https://en.wikipedia.org/wiki/Pok%C3%A9mon_(video_game_series)" target="_blank">Pokemon</a></i> is the most famous, often keep status effects relevant by making everyone operate under the same rules.</li>
<li>Status effects are often vital to strategy in the <i><a href="https://en.wikipedia.org/wiki/Shin_Megami_Tensei" target="_blank">Shin Megami Tensei</a></i> series.</li>
<li><i><a href="https://store.steampowered.com/app/706560/Jimmy_and_the_Pulsating_Mass/" target="_blank">Jimmy and the Pulsating Mass</a></i> streamlines the experience by only giving players access to a few status effects, mainly damage-over-time and a one-turn stun effect. No enemies are immune, even bosses. It is also only possible to protect against a few "basic" status effects; though this may seem unfair, it means no status effect can be totally debilitating, and forces players to actually figure out how to survive status effects instead of just ignoring them.</li>
<li><i><a href="http://site.scfworks.com/?page_id=8" target="_blank">Last Scenario</a></i> lets enemies use status effects against you relentlessly, but doesn't give you access to any. Much of the game's strategy involves figuring out how to block and mitigate status effects; until the endgame, your options are limited, so you must pick and choose which ones you can ignore and which ones you will have to endure.</li>
<li>The <i><a href="https://en.wikipedia.org/wiki/Monster_Hunter" target="_blank">Monster Hunter</a></i> series features a similar system to my tolerance mechanic, where attacking bosses with status effects will drain a resistance meter until they are finally applied. I actually wasn't aware of this at all when I designed my system, but it's good to know there's another example out there!</li>
</ul>
<h2 id="targeting">Targeting and AI</h2>
I hate random targeting systems in RPGs. Hate hate hate. RPGs often balance more powerful or useful characters by giving them poor defense, but if you have no way to meaningfully protect them, that just makes the battle a complete crapshoot. If enemies all gang up on your tough fighter, you'll be fine; if they all gang up on your healer every turn, you're screwed. It's the absolute worst system possible: You <i>know</i> the problem and the optimal outcome, but you're powerless to influence it. That's not fair and it's not fun.
...But you have to admit, making sophisticated AI is hard -- and a completely deterministic enemy becomes a solved game. A common compromise is to make targeting random by default, but give the player means to nudge the dice or influence who can and can't be attacked. In <i>Cartoon Battle</i>, I provided this through the Protector, Sneak, and Martyr abilities. It's possible for enemies to gang up on your squishy Mage if you're unlucky, but you can completely eliminate that possibility if you protect them -- at the cost, of course, of sacrificing Fighter's own turn. There's still uncertainty and risk, but it's a risk you can weigh and calculate. Other games use different methods, such as aggro systems that allow you to influence the enemy's behavior through other factors.
<h3 id="targeting.smart">Smart Targeting</h3>
The standard targeting system in the default engine is just random selection, nothing much interesting there. But I also added additional code for "smarter" targeting behavior. These procedures came logically from me imagining how I or another human player would think when selecting a target, and emulating that as best I could.
In the default engine, there is an effect, "Off-Balance", that makes the character more vulnerable to direct damage, but only lasts for one turn. Obviously, an attacker would want to prioritize anyone with this status (unless, of course, they were using a non-damaging move). Similarly, there is an effect, "Knocked Down", that sharply reduces a character's Defense stat -- also an obvious priority target. I included handlers for these effects that made the enemies prioritize them -- either to the exclusion of all else (on "hard" difficulty) or just having a higher chance to select them (on "medium" difficulty).
There is also, in the default engine, an additional wrinkle with debuff abilities: They are influenced by the target's Special stat, and so will be more effective if the target has reduced Special due to a debuff. So in the case that the enemy was using a debuff ability, I added another handler that made them prioritize such targets.
That's all the smart targeting I included in the default engine, but you could go a lot further. As a player, you probably like to focus your fire on the target with the lowest health or defense to take them out quickly; maybe enemies should do the same? You probably also recognize that certain types of enemies, like healers or mages, are more dangerous and should be taken out first; maybe enemies should be more inclined to target certain characters based on their class or other attributes. Think about your thought process when you select a target, and how you might translate that into programming logic.
This makes the enemies harder, but in a way it also makes the battles fairer. These outcomes are much worse for the player; but if we left it entirely up to random chance, players would have wildly different experiences depending on if the random selector chose a more vulnerable character or not. By making the enemies always prioritize vulnerable targets, players can't just rely on good luck to get them through the battle; they must actually pay attention to their defenses and protect characters appropriately.
<h3 id="targeting.aggro">Aggro Systems</h3>
The basic idea behind aggro systems is a logical one. If you think about how a player prioritizes targets, they make their decisions based on what abilities the enemies use, right? Obviously you'll want to defeat a healer before anything else, and then probably you'd want to go after a dangerous mage before a tough but less remarkable fighter. Aggro systems emulate this behavior by giving every action a statistic that draws enemy attention. The exact mechanics vary: Usually, doing more damage draws more attention, but healing and support abilities can also be made to draw aggro. The key advantage of such a system is that, by quantifying levels of enemy attention, you can give "tank" characters a lot more to do, such as "Taunt" abilities that draw additional aggro towards them, or an inverse ability that removes aggro from themselves or others.
The aggro system in the default engine is based on the one used in <i>Flawed Crystals</i>, a game made by A Friendly Irin in this engine, which was in turn based on the system used by the <i>Dragon Age</i> series of video games. You can read about how the system works in <i>Dragon Age</i> <a href="https://dragonage.fandom.com/wiki/Threat" target="_blank">here</a>.
You should notice that this system makes some key assumptions and tradeoffs that influence what the resultant gameplay will look like. The key means of drawing threat is through direct damage, which will draw enemy attention towards offensive characters and away from support characters. Moreover, threat gain is based not on total but <i>proportional</i> damage; this means that weak enemies who can be dispatched in a few hits will be more influenced by direct attacks, while strong enemies with lots of HP will be less easily moved. However, the corollary to this is that enemies with low dependence on direct attacks will be more strongly influenced by abilities that generate flat threat increments, such as the "Taunt" skill in <i>Dragon Age</i>.
You may note that one major departure from <i>Dragon Age</i> is that this system is actually less deterministic. In <i>Dragon Age</i>, enemies will <i>always</i> target the character closest to them who has the highest threat value. In our system, having the highest threat gives you a greater chance of being targeted, but it is not a guarantee. This is an important consideration in aggro systems: how strongly do you want it to control enemy behavior? Too much and it may, again, lead to a solved game where the player can easily direct everyone's attention to the tank, who shrugs off every blow while the mages lay waste to your foes with impunity. You would have to carefully tweak the tank's stats such that they cannot survive a constant onslaught and must occasionally pass the buck to someone else, and perhaps give enemies area-of-effect attacks that the tank cannot protect everyone from.
Another interesting feature of this system is that enemies do not coordinate. Threat is not universal; every enemy has their own unique set of threat values towards each character. An enemy will not bat an eye at a character annihilating their neighbor, so long as they aren't hit too. It's a lot simpler to consider every enemy in isolation like this, but that's not terribly realistic, is it? If an enemy starts wailing on a vulnerable character, you'd focus your fire to take them out quickly. It's worth considering how you might change the existing code to accomplish this, and why. (A common method is to tie aggro to the player characters, rather than every enemy having their own unique hitlist, and using this value to influence all enemies simultaneously.) This can make enemies a lot more dangerous, but their behavior will feel more like fighting against a human player.
Finally, note a contradiction in this system: I said that a human player would prioritize targeting enemies with support skills, but under this system, enemies do not register support characters at all! This can easily lead to very unbalanced gameplay where the healers can stay back and just keep healing the tanks continuously, who will continue drawing aggro and leave the healers untouched. The easy solution to this is to make support skills draw aggro too, but too much and you may end up tipping in the other direction, with supporters getting too quickly swarmed to use their abilities effectively. You can also generate an unstable feedback loop where a healer in danger needs to heal themselves, which in turn draws more aggro and gets them attacked more...
As with all things in design, this is a balancing act. How realistic do you want the enemies' behavior to be? How much do you want to force the player to outwit them and balance their actions? What behaviors do you want to incentivize or disincentivize? These are complicated questions, and the difficulty in answering them may explain why so many designers choose to make the AI more random and just give enemies more power to make up the difference.Use the table of contents to navigate. You can come back to the top at any time by pressing the "home" key on your keyboard.
This introduction will teach you how to add content to the default engine settings. For advanced documentation and advice on customization, see [[Documentation (Advanced)]]. This documentation will assume you are familiar with SugarCube basics; see <a href="https://www.motoslave.net/sugarcube/2/docs/" target="_blank">the SugarCube documentation</a> for reference.
<h1>Table of Contents</h1>
><a href="#databases">Objects, Classes, and Databases</a>
><a href="#data.puppets">Creating Puppets</a>
><a href="#data.actions">Creating Actions</a>
><a href="#data.effects">Creating Effects</a>
><a href="#data.items">Creating Items</a>
><a href="#data.enemies">Creating Enemies</a>
>><a href="#enemyactions">Enemy Actions</a>
>>><a href="#enemyactions.finn">Simple Example: Finn</a>
>>><a href="#enemyactions.bubblegum">Advanced Example: Princess Bubblegum</a>
>>><a href="#enemyactions.bonnibel">Complex Example: Bonnibel</a>
><a href="#encounters">Creating Encounters</a>
><a href="#customization">Customization</a>
>><a href="#custom.init">Initial Variables</a>
>><a href="#custom.passages">Customizing Passages</a>
<h2 id="databases">Objects, Classes, and Databases</h2>
RPGs tend to involve a lot of <i>stuff</i>. Heroes, stats, items, equipment, spells, enemies... It can get dizzying quite quickly. Fortunately, the engine is set up to make creating these objects easy.
An <i>object</i>, in programming, is a general term for a data structure that contains other data structures within it. Objects are very useful for keeping track of large sets of data. For example, say are the manager of a company and want to maintain a digital list of employees. You might want to keep track of various attributes like the name, age, and income of every employee. It would be tedious and confusing to have to keep track of each of these variables if they were not connected in any way. Imagine having to make separate variables not only for every employee, but every <i>attribute</i> of every employee! You'd have to give them names like {{{John_age}}}, {{{Jane_name}}}, {{{Jane_income}}}, and {{{John_name}}}, all floating around in a huge confusing mess.
With objects, you could simply collect all these variables under one person, using the curly brace <code>{}</code> operators:
{{{
var John = {
firstName: "John",
lastName: "Smith",
age: 18,
income: 27
}
}}}
From then on, every time you want to access one of John's sub-variables, you cand simply place a dot ({{{.}}}) symbol after "John" object to call them. {{{John.firstName}}} will return "John", {{{John.age}}} will return "18", and so on. These sub-variables are called <b>properties</b> or <b>attributes</b>, and can, themselves, contain objects (or even functions). You can call up attributes of attributes the same way, with another dot operator, as many times as you need. For instance, if we split John's {{{income}}} attribute into two properties, {{{hourly}}} and {{{yearly}}}, we could write {{{John.income.hourly}}} to get his hourly wage.
Objects are helpful, but for something as complicated as a game, we need to go one step further, and turn them into <b>classes</b>. Classes can be thought of as a template or assembly line to produce many copies the same object. Classes of objects all share the same structure, construction method, and functions. This is very useful in RPGs, where we may want to classify our data into broad groups -- say, characters, actions, and items -- that each have unique data but should each behave similarly. For instance, all characters in an RPG should have attributes like HP and Attack. By defining a class, we can create a general template for an object that will always be constructed through the <b>constructor</b> method defined within it.
To create an object via a class template, you use a different syntax than defining regular objects. You use the keyword {{{new}}} paired with the class name, like so:
{{{
var John = new Employee("John Smith");
}}}
You will always need to pair a class instantiation with a function call {{{()}}}; this calls the constructor function used to create the object. You can pass arguments to the constructor like any other function to modify the instance being created; for instance, in this code, the constructor might be able to use the argument "John Smith" to assign the {{{Employee}}}'s {{{firstName}}} and {{{lastName}}} attributes.
(For more information, see w3school's tutorials on <a href="https://www.w3schools.com/js/js_objects.asp" target="_blank">objects</a> and <a href="https://www.w3schools.com/Js/js_classes.asp" target="_blank">classes</a>.)
The details of how our classes are defined are explained in [[Documentation (Advanced)]], but to get started, all you need to do is provide data for your objects. This is handled through <b>database</b> passages, found in the "custom" subfolder of the "javascript" folder.
Each database is handled similarly. We create a database object as the attribute of the {{{setup}}} variable, which is a special variable used by SugarCube. (Assigning databases to {{{setup}}} is important for reasons <a href="https://www.motoslave.net/sugarcube/2/docs/#guide-state-sessions-and-saving-refreshing-and-restarting" target="_blank">explained in the SugarCube documentation</a>.) Each property of this database object corresponds to a single object we want to define, and contains data that will be read to create an instance of the object when the game is up and running. Once you have a fully-defined database entry, you need only pass the entry's name as the argument to the object constructor, and you'll create a live instance of that object.
<h2 id="data.puppets">Creating Puppets</h2>
The class for playable characters is called "Puppet", as a carry-over joke from <i>Cartoon Battle</i>. As such, the database object is called {{{puppetData}}}.
Puppet data entries look like this:
{{{
"Fighter": {
"gender": 'N',
"hp": 1000,
"stats": {
"Attack" : 30,
"Defense" : 50,
"Special" : 10
},
"actions": [
"Firefly",
"Sword",
"Punch",
"Hammer",
"Bull Rush",
"Assault",
"Meditate",
"Berserker",
"Defender",
"Protector",
"Martyr"
],
"defaultAction": "Sword",
"crisis": "Perfect Defense",
"specialInit": function (actor) {
actor.firefly = true;
}
}
}}}
This isn't what a live {{{Puppet}}} object looks like, but it contains all the data the {{{Puppet}}} constructor needs to create one.
* {{{gender}}} determines what pronouns the system will use to refer to the character. You can spell out "male", "female", or "neutral", or use the one-letter shorthand. If undefined, pronouns will default to "it/its".
* {{{fullname}}} is a string; this is the name that will be displayed in the actor box. Defaults to the character's regular name.
* {{{hp}}} determines the character's starting number of hit points. This determines how much damage they can take before they are defeated.
* {{{stats}}} is, itself, an object. You can define any stats you want here, but the defaults are {{{Attack}}}, {{{Defense}}}, and {{{Special}}}. The character will be created with stats equal to the numbers assigned here.
* {{{actions}}} is an array listing the actions this character can take in battle. You only need to provide the names of each action here; see <a class="noExternal" href="#data.actions">Creating Actions</a> for details on action construction.
* {{{equipSlots}}} is an object; the keys are the names of equipment slots, and the value is the number of subslots. Defaults to the object specified in {{{DEFAULT_EQUIP_SLOTS}}}.
* {{{defaultAction}}} determines what action will be used when the player uses the {{{[Q]}}} shortcut in battle.
* {{{crisis}}} determines the character's Crisis ability. Like with regular actions, only the name is required. An array can be used here to give a character multiple Crises. If undefined, the character simply won't have a Crisis ability.
* {{{tolerances}}} is an object that sets the character's ailment tolerances. The character must be hit with the ailment a number of times equal to 1 plus the tolerance before it will apply. A value of -1 conveys total immunity to the ailment.
* {{{elements}}} is an object that sets the character's elemental affinities. Note that these are multipliers, not percentile values; e.g. a value of 2 will make the character take double damage, and a value of -1 will heal the character for whatever damage the attack would have normally done. The property names must match elements defined in {{{ELEMENT_LIST}}}.
* {{{respawn}}} is an integer; after the character is defeated, they will be revived after this many turns. If unset, the character will never respawn automatically.
* {{{retaliations}}} is an integer; sets how many times the character can counterattack on their turn. Note that you will also need to assign a {{{counter}}} property for it to trigger.
* {{{deathMessage}}} is a string; printed when the character is defeated (HP reaches 0). Defaults to "{{{<character name> is defeated!}}}".
* {{{specialInit}}} is a function that is run at the end of the {{{Puppet}}} constructor. This is a catchall that allows you to tweak any other variables you want that aren't specified by the regular attributes. (Due to a coding quirk, the object under construction needs to pass itself as an argument to this function, so assign your code to {{{actor}}} rather than {{{this}}}.)
* {{{immortal}}} is a Boolean; if {{{true}}}, the character will not be defeated if their HP reaches 0. (Must be assigned through {{{specialInit}}}.)
* {{{maskhp}}} is a Boolean; if {{{true}}}, the character's HP bar and HP total will not be displayed in their actor box. (Must be assigned through {{{specialInit}}}.)
* {{{showMaxHP}}} is a Boolean; if {{{true}}}, the character's max HP will be displayed alongside their current HP in their actor box. (Must be assigned through {{{specialInit}}}.)
* {{{large}}} is a Boolean; if {{{true}}}, the character's actor box will span the full width of the screen. (Must be assigned through {{{specialInit}}}.)
* {{{caps}}} is a Boolean; if {{{true}}}, the character's name will be capitalized in their actor box. (Must be assigned through {{{specialInit}}}.)
<h2 id="data.actions">Creating Actions</h2>
Actions determine what things your characters can do in battle -- attacking, casting spells, etc. The database object for actions is called {{{actionData}}}, and its entries look like this:
{{{
"Sword": {
"cost": 2,
"weight": 1,
"basic": true,
"info": function (action) {return `Attack with a weight of ${action.weight}.`},
"desc": `Ah, the sword: favored weapon of heroes everywhere. In reality they're pretty impractical and hard to use, but they just look so cool!`,
"useText": null,
"actText": function () {
return `$B.subject.name swings their sword with perfect form.`;
},
"act": justdmg
}
}}}
This isn't what a live {{{Action}}} object looks like, but it contains all the data the {{{Action}}} constructor needs to create one. There are additional attributes not shown in this entry that are detailed below:
* {{{useText}}} is displayed in bold before the action's description in the action phase. Set this to {{{null}}} to omit that section entirely. By default, this text is {{{<user>.name uses "<action>.name".}}}, quotes included.
* {{{actText}}} is a prose description of the action. It is entirely optional, and only for flavor. (Note that if you want to use variables such as the user's name in this description, you will need to make it a function that returns a string; if you write it as a raw string, the variable you need won't exist when the {{{Action}}} object is created!)
* {{{act}}} is the code for what the action actually does in a gameplay sense. It is recommended to make this function return a string of SugarCube code containing your real code, as it is much easier to print text to the screen with SugarCube, and many gameplay functions are contained in SugarCube widgets. Alternatively, there are a number of generic action functions you can assign here. (See the file {{{2_action-functions.js}}} for details.)
* {{{preview}}} is a function that provides a projection of the action's effects in the confirm phase. Set to {{{null}}} to display nothing. By default, it previews the damage of a basic attack.
* {{{info}}} provides information about the action's gameplay function to the player, and is displayed in the action's help description. (Note that if you want to reference data values of the action, you will need to make this a function, as shown in the example entry.)
* {{{desc}}} is flavor text describing the action, and is displayed below {{{info}}} in the action's help description. Optional.
* {{{phase}}} is a string; forwards the player to the matching battle phase passage when the action is selected. Must match a passage name EXACTLY. Defaults to "targeting phase".
* {{{target}}} is a string; can take a value of "enemy", "ally", or "all". Determines which party or parties can be targeted by the action. Defaults to "enemy".
* {{{cost}}} is a nonnegative integer; determines how many EN points the action consumes when used.
* {{{hpcost}}} is a nonnegative integer; determines how many hit points the action consumes when used. Defaults to 0.
* {{{weight}}} is a number; multiplied by the user's {{{Attack}}} stat when determining the damage of an attack. (See the damage formula for a deeper explanation.)
* {{{effweight}}} is a number; determines the strength of any status effects applied by the action.
* {{{dur}}} is an integer; determines the duration of any status effects applied by the action.
* {{{accuracy}}} is a nonnegative integer; equal to the percentile value of the action's chance to hit. Set to {{{true}}} to make the action always hit. Defaults to {{{true}}}.
* {{{critRate}}} is a nonnegative integer; equal to the percentile value of the action's chance of landing a critical hit. Defaults to 0.
* {{{critMultiplier}}} is a nonnegative number; damage is multipled by this value if the attack lands a critical hit. Defaults to 1.5.
* {{{element}}} is a string; determines the action's elemental property. Must match one of the elements defined in {{{ELEMENT_LIST}}}. Optional.
* {{{useSpecial}}} is a number between 0 and 1; determines proportion of base damage dependent on Special. Defaults to 0.
* {{{uses}}} is a nonnegative integer; determines how many times an action can be used before its uses must be refilled. If not defined, this attribute will not be set. Optional.
* {{{cooldown}}} is a nonnegative integer; determines how many turns must elapse after use before the action can be used again. If not defined, this attribute will not be set. Optional.
** {{{enemyCD}}} is a nonnegative integer; determines the cooldown of this action when used by an enemy. Defaults to {{{cooldown}}}. Optional.
** {{{nameCD}}} is a string; determines the key in the enemy's {{{cd}}} Map the cooldown will reference when set. Defaults to the action's name. Optional.
* {{{warmup}}} is a nonnegative integer; determines how many turns must elapse from the start of a battle before the action can be used. Requires a defined {{{cooldown}}} attribute. If not defined, this attribute will not be set. Optional.
* {{{formula}}} is a function; if defined, it will be used in place of the regular formula when calculating attack damage.
* {{{basic}}} is a Boolean; if {{{true}}}, the action can be used when the character is dizzy.
* {{{pierce}}} is a Boolean; if {{{true}}}, the attack ignores the target's {{{Defense}}} stat when calculating damage.
* {{{instant}}} is a Boolean; if {{{true}}}, the action will not end the user's turn.
* {{{counter}}} is a Boolean; if {{{true}}}, the action is considered a counterattack action.
* {{{noself}}} is a Boolean; if {{{true}}}, the user cannot target themselves with this action.
* {{{oncePerTurn}}} is a Boolean; if {{{true}}}, the action can only be used once per turn, even if it is instant.
* {{{noShock}}} is a Boolean; if {{{true}}}, the action will not cure effects that can be cured through direct damage.
* {{{silent}}} is a Boolean; if {{{true}}}, action code will execute without any display in the action phase.
* {{{truce}}} is a Boolean; if {{{true}}}, it won't violate the enemy's surrender. By default, this is {{{true}}} for {{{silent}}} actions, and {{{false}}} otherwise.
* {{{noDefault}}} is a Boolean; if {{{true}}}, the action cannot be set as a character's default action. By default, this is {{{false}}} if the action inflicts damage, and {{{true}}} otherwise.
* {{{nosave}}} is a Boolean; if {{{true}}}, the action will not be saved as the character's last action after use.
* {{{saveMod}}} is a string; the user's last action will be set to the action named with {{{saveMod}}}. Defaults to the action itself.
* {{{crisis}}} is a Boolean; if {{{true}}}, the action is a Crisis action and can only be used by fully consuming the user's Crisis points.
* {{{ranged}}} is a Boolean; if {{{true}}}, the action will ignore range limitations on the battle grid (see [[Additional Features]]).
<h2 id="data.effects">Creating Effects</h2>
Status effects change characters in some way. They can be beneficial, like boosting one or more stats, or detrimental, like preventing the character from using certain actions. (For more information, see [[Design]].) The database object for effects is called {{{effectData}}}, and its entries look like this:
{{{
'ATK Boost': {
"buff": true,
"stackable": true,
"statmod": true,
"onApply": function (puppet) {
this.id = puppet.stats["Attack"].addMod("ATK Boost",this.power);
},
"onRemove": function (puppet) {
puppet.stats["Attack"].removeMod("ATK Boost",this.id);
},
"info": function (effect) {
return `Attack boosted by ${this.power}.`;
},
"addText": function (target) {
return `${target} is surging with strength!`;
},
"removeText": setup.effectFunctions.remBuff
}
}}}
This isn't what a live {{{Effect}}} object looks like, but it contains all the data the {{{Effect}}} constructor needs to create one. There are additional attributes not shown in this entry that are detailed below:
* {{{onApply}}} is a function run when the effect is applied. It takes the target character as an argument. As an example, this function may involve changing a stat or applying a special flag.
* {{{onRemove}}} is a function run when the effect is removed from a character. It takes the target character as an argument. This function usually reverses the effects of {{{onApply}}}, but may include additional code.
* {{{info}}} is a description of the effect, printed in the status pane. If you want this description to reference attributes of the effect, you will need to make this a function.
* {{{addText}}} and {{{removeText}}} are text messages displayed when the effect is added or removed, respectively. It takes the host character's name as an argument.
** There are default text formats defined in {{{setup.effectFunctions}}}, above the database.
* {{{shock}}} is an integer between 1 and 100; corresponds to the percent chance of direct damage removing the effect. Optional.
* {{{topDec}}} is a Boolean; if {{{true}}}, the effect will decay at the start of the round. By default, effects decay at the end of the round.
* {{{sticky}}} is a Boolean; if {{{true}}}, the effect cannot be removed through normal means.
* {{{ULTIMATESTICKY}}} is a Boolean; if {{{true}}}, the effect cannot be removed through <i>any</i> means except ending the battle.
* {{{unblockable}}} is a Boolean; if {{{true}}}, the effect cannot be blocked by protective effects such as Stasis.
* {{{synonym}}} is a string; if set, tolerance calculations will use the tolerance value specified by this string instead of the effect's own name. Note that this must match a valid tolerance name EXACTLY.
* {{{persistAfterBattle}}} and {{{persistAfterDeath}}} are Booleans; if {{{true}}}, the effect will persist past their respective events. By default, effects are always removed after battle and on character defeat.
* {{{buff}}} is a Boolean; if {{{true}}}, the effect is considered positive. Relevant to some actions and system messages.
* {{{stackable}}} is a Boolean; if {{{true}}}, multiple instances of the same effect can exist on the same character.
* {{{exclusive}}} is a Boolean; no two {{{Effect}}}s with {{{exclusive}}} set to {{{true}}} can exist at the same time on the same character.
* {{{statmod}}} is a Boolean; if {{{true}}}, the effect modifies a character's stats. Relevant for some effect calculations.
* {{{untargetable}}} is a Boolean; if {{{true}}}, the effect prevents a character from being directly targeted by attacks. This property will hold as long as a character has any effects with this property.
* {{{uncontrollable}}} is a Boolean; if {{{true}}}, the effect is considered a loss-of-control effect. Characters will be uncontrollable as long as they have any effects with this property.
* {{{hold}}} is a Boolean; if {{{true}}}, the effect is considered a hold action that restricts the victim from moving. As long as a character has any hold effects, they cannot act.
** {{{holdAction}}} is a function for hold effects; it returns the dummy action that will be displayed if an enemy is held by the effect. This is usually just text, but may include other functionality.
** {{{priority}}} is an integer; determines the order in which enemies must resolve hold effects. Lower numbers are resolved first.
* {{{dot}}} is a Boolean; it is short for "damage over time". If {{{true}}}, the effect is a damage-over-time effect.
** {{{dmgtype}}} is a function; determines how the damage-over-time is calculated. Several pre-written functions are specified in {{{setup.effectFunctions}}}.
** {{{msg}}} is the message displayed when the damage-over-time triggers.
** {{{weight}}} determines the strength of the damage-over-time. For most effects, this is calculated as part of the action that applies the effect, but some damage-over-time effects use a fixed weight.
* {{{shield}}} is a Boolean; if {{{true}}}, this is a "shielding" effect and will block attacks.
** {{{uses}}} is a nonnegative integer; sets the number of hits a shielding effect can block.
** {{{onHit}}} is a function run when the shield is hit. This may just be a message, or it can include other functionality.
<h2 id="data.items">Creating Items</h2>
Items are objects that characters can obtain during gameplay. They may be consumed on use for a one-time benefit, such as a healing potion, or they may be equipment that grants some bonus when equipped. The database object for items is called {{{itemData}}}, and its entries look like this:
{{{
"Apple of Life": {
"usable": ["inmenu"],
"onUse": function (puppet) {
puppet.maxhp += 100;
inv().decItem(this.name);
return;
},
"info": "Permanently increases max HP by 100.",
"desc": `The apple Adam and Eve didn't eat. It's lost much of its power, this far from the Garden, but it'll still make a puppet a little more vivacious than usual.`
}
}}}
This isn't what a live {{{Item}}} object looks like, but it contains all the data the {{{Item}}} constructor needs to create one. There are additional attributes not shown in this entry that are detailed below:
* {{{info}}} and {{{desc}}} function exactly like they do in <a class="noExternal" href="#data.actions">Actions</a>.
* {{{usable}}} is an array that can have up to two elements: "inmenu" and "inbattle". This determines if the item can be used in the party menu, in battle, or both (if both strings are included in the array). Optional.
** {{{onUse}}} is a function that determines what happens when the item is used in the party menu.
** {{{action}}} is a string; name of the action the item will generate if used in-battle. This defaults to the item's name.
* {{{value}}} is an integer; determines the units of currency the item can be bought and sold for at shops.
* {{{fakeName}}} is a string; alternate name that will be displayed until the item is used or equipped. Optional.
Equippable items have several additional properties. An example entry looks like this:
{{{
"Symbol of Destruction": {
"equippable": {slot: "Weapon", tags: ["symbol"], restrictedTo: []},
"onEquip": function (puppet) {
puppet.stats["Attack"].addMod("Symbol of Destruction",5,true);
},
"onRemove": function (puppet) {
puppet.stats["Attack"].removeMod("Symbol of Destruction");
},
"desc": "A weapon.",
"info": "ATK +5"
}
}}}
* {{{equippable}}} is an object with three sub-properties. Its existence is checked to determine if an item can be equipped.
** {{{slot}}} is a string; states what slot the item will be equipped to. Must match a character equipment slot.
** {{{restrictedTo}}} is an array; contains the names of characters who can equip the item. Make the array empty for no restrictions.
** {{{tags}}} is an array; stores any additional metadata, such as e.g. equipment restrictions by character class. Optional.
* {{{onEquip}}} and {{{onRemove}}} are functions determining the behavior of the item when it is equipped and removed, respectively.
* {{{sticky}}} is a Boolean; if {{{true}}}, the item is considered cursed and can only be removed through a decurse station.
Note that items are stored in an {{{Inventory}}} object, the code for which can be found in {{{class-item.js}}}. Use the {{{addItem}}} and {{{removeItem}}} functions when adding or removing items to an inventory.
<h2 id="data.enemies">Creating Enemies</h2>
Enemies are the opponents your player will face off against in battles. They are similar to {{{Puppet}}} objects in most respects, but contain several key differences. The database object for enemies is called {{{enemyData}}}, and its entries look like this:
{{{
"Dipper": {
"bestiaryNo": 0,
"alts": ["Big Dipper"],
"gender": 'M',
"hp": 1000,
"stats": {
"Attack" : 25,
"Defense" : 30,
"Special" : 35
},
"elements": {
"black" : 0.8,
"white" : 1.2,
"blue" : 0,
"yellow": -0.1
},
"cooldown": {
"Dispel Magick": 0
},
"actions": function () {
while (V().action === null) {
(...)
} // end loop
return;
}
}
}}}
This isn't what a live {{{Enemy}}} object looks like, but it contains all the data the {{{Enemy}}} constructor needs to create one. Most attributes common to {{{Puppet}}} entries work the same here, but additional attributes are detailed below:
* {{{cooldown}}} is an object that determines the cooldown for the enemy's actions. You can have multiple property names for different actions.
* {{{noAttacks}}} is a nonnegative integer that determines how many actions the enemy can take per round. By default, this is 1.
* {{{priority}}} is a number that determines the order in which enemies will act during the enemy turn. By default, this is equal to their index in the enemy party array. Lower numbers act first.
* {{{mercy}}} is a number; determines the chance of an enemy skipping smart targeting. (See [[Documentation (Advanced)]] for details on targeting logic.)
* {{{surrender}}} is a function; run when an enemy is spared (if the option is available).
* {{{surrenderFail}}} is a function; run when the player violates an enemy's surrender by using a damaging action.
* {{{xp}}} and {{{gp}}} are the number of experience points and currency points, respectively, the enemy awards on defeat. Must be integers. Defaults to 0 in both cases.
* {{{itemDrops}}} is an object. Property names must correspond to items, and values must be integers between 1 and 100. Values are the percentile chance of the respective item dropping upon the enemy's defeat.
* {{{boss}}} is a Boolean; denotes that this is a boss enemy. Bosses are treated differently by some system code. (Must be assigned through {{{specialInit}}}.)
* {{{specialdeath}}} is a string; the player will be forwarded to this passage when the enemy is defeated. (Must be assigned through {{{specialInit}}}.)
* {{{bestiaryNo}}} is an integer that determines the order in which the enemy appears in the bestiary, if there is one.
** {{{alts}}} is an array of strings listing any alternate forms to display in the bestiary entry. Optional.
** {{{desc}}} is a description to be displayed in the enemy's bestiary entry.
<h3 id="enemyactions">Enemy Actions</h3>
The most complicated attribute for enemies is {{{actions}}}. Unlike player characters, enemies don't have any human mind to pick their actions for them, so it's not enough to just list what actions they can use. We have to provide a logic for how and when they should act as well.
Here is an example of the beginning of one enemy's logic function:
{{{
"actions": function () {
while (V().action === null){
var act = random(1,100);
if (act <= 20){
$.wiki('<<randomTarget>>');
V().action = new Action("Cuteness Poisoning");
V().action.weight = 1;
V().action.effweight = 0.3;
V().action.dur = 3;
V().action.actText = function () {return ``+
`Mabel conjures an image of a kitten... no, wait, <i>two</i> kittens? And they're riding on a <i>puppy</i>? Omigosh, it's so cute you could just <i>DIE!</i>`;};
V().action.act = dmgandeffect('t',"Poisoned");
}
}}}
Let's break this down.
{{{
while (V().action === null)
}}}
We generally want to wrap this whole function in this {{{while}}} loop. There will be some random variance and conditionals when choosing an action for an enemy, so an action may not always be chosen on the first pass. This loop can only be exited if an action is chosen, which ensures that an action <i>will</i> be chosen for the enemy. (If not, all sorts of bugs could occur when the engine tries to read a {{{null}}} action.) <b>However, be wary of infinite loops.</b> Make sure it is always possible for this function to pick some action under any circumstance, even if it's only a dummy action. Otherwise, the program will be stuck running this loop forever, which will prevent the program from continuing and crash the user's browser.
{{{
if (act <= 20)
}}}
When an enemy has multiple actions, they're chosen through an if-else tree. The program will test each conditional in order to find what action to select. Typically, there is an element of randomness to this, as enemies could become solved games if they always behaved exactly the same way.
(I align these conditionals with the same indentation as the {{{while}}} loop. Formatting standards typically suggest indenting everything within a loop, but I find it easier to keep them at this level due to the amount of data inside them. You can format them either way.)
{{{
if (this.CDcheck("Glitter Bomb") && act <= 60 && act > 40)
[...]
}
}}}
It is possible to use further conditionals to implement more complex behavior and disqualify a selected action based on other factors, such as this example from Mabel's Glitter Bomb ability. If Glitter Bomb hasn't finished its cooldown (which we can find with {{{CDcheck()}}}, a function common to all {{{Enemy}}} objects), it can't be selected, even if it otherwise falls into the correct {{{act}}} threshold. Thanks to the {{{while (V().action === null)}}}, a new action will be chosen afterwards.
{{{
$.wiki('<<randomTarget>>');
}}}
When an action is selected, the first thing we need to do is select a target, just as the player selects a target before executing their action. (This is necessary to do early, because some flavor text references the target, and sometimes there may not be a viable target at all, for some special actions.) We can do this through the {{{$.wiki()}}} command, which allows SugarCube code to be executed in JavaScript.
(There is an issue with using {{{$.wiki()}}}, however: it can't print any text. If we want our {{{<<randomTarget>>}}} widget to display any text, such as an alert that a character was protected, it won't show up here. This is why we had to save the text to a {{{_msg}}} variable and print it later.)
{{{<<randomTarget>>}}} will work for most purposes, but to learn more about the targeting logic and customizations you can make, see the relevant section in [[Documentation (Advanced)]].
Then we can define the statistics of the action, either manually (as in this example) or through a call to the actions database. In the default examples, action values are all set manually within these conditionals. I like this method because it allows me to see both the action statistics and the logic used to trigger them at the same time, which is helpful for balance tweaking. In <i>Cartoon Battle</i> every enemy was a set piece with unique abilities, but if you want to create an RPG with enemies who share general actions, you may want to define them separately in the database.
(Note that due to the way Action objects work, if you define your actions manually instead of referring to a database, you will need to place an underscore {{{_}}} before each property name; otherwise, you'll produce an error. See [[Documentation (Advanced)]] to learn why this is.)
These are the basic components of enemy behavior. The default engine includes the behavior of every first-tier enemy in <i>Cartoon Battle</i> for you to examine to get a sense of how it works in practice. I won't go over all of them here, but let's look at Finn and Princess Bubblegum as simple and complicated examples, respectively.
<h4 id="enemyactions.finn">Simple Example: Finn</h4>
{{{
this.actions = function () {
while (V().action === null){
var act = random(1,2);
if (act == 1) {
$.wiki('<<randomTarget>>');
V().action = new Action("Sword");
V().action.actText = "Finn swings his sword wildly!";
V().action.useText = null;
} else if (act == 2) {
$.wiki('<<randomTarget>>');
V().action = new Action("Charge");
V().action.actText = "Finn charges in recklessly!";
V().action.useText = null;
}
} /* end loop */
return;
}
}}}
Finn shows us the basic structure of how enemy actions are structured. We set the variable {{{act}}} based on some logic -- here, randomly -- and then create an {{{if}}} tree keyed to it, with actions in each branch.
Finn has only two actions: a regular attack, and a reckless charge. Both of them begin by determining a target with {{{<<randomTarget>>}}}; then the {{{$action}}} variable is set through a call to the {{{Action}}} constructor; then they set the attributes of the action. In this case, we don't need to do much to define the actions, as they are intentionally exact copies of extant actions: Fighter's Sword and Charge skills. We only need to alter the flavor text. If you plan to have multiple enemies use the same actions, it may be a good idea to define them in the action database so you don't have to set attributes here.
You can see here that Finn has very simple behavior: half the time he will use a regular attack, and half the time he will use a stronger attack that leaves him open. This behavior is totally random; he won't avoid using the charge attack if he's low on health or afflicted with a Defense debuff, as the player might in his position. This is a lot easier to code, but it also aligns well with how I wanted Finn to behave. In <i>Adventure Time</i>, Finn is a reckless and foolhardy character, so this behavior is fitting for his personality.
<h4 id="enemyactions.bubblegum">Advanced Example: Princess Bubblegum</h4>
{{{
"cooldown": {
"mass buff": 0,
"Chi Shield": 0
},
"actions": function () {
while (V().action === null){
var act = random(1,100);
if (this.cd.get("mass buff") < 0) {
act = random(1,3)
switch (act) {
case 1:
V().action = new Action("Call to Arms");
break;
case 2:
V().action = new Action("Walled City");
break;
case 3:
V().action = new Action("Age of Enlightenment");
break;
}
this.cd.set("mass buff",2);
}
else if (this.ready) {
act = random(1,2)
if (act == 1) {
$.wiki('<<randomTarget>>');
V().action = new ItemAction("Grenade");
V().action.weight = 1;
V().action.useText = null;
V().action.actText = `Princess Bubblegum throws what appears to be a giant peppermint, but as soon as it hits the ground it explodes with the force of a grenade, spreading hard candy shrapnel everywhere.`;
}
else if (act == 2) {
$.wiki('<<randomTarget "ignore downed" "debuff">>');
V().action = new Action();
V().action.effweight = 0.8;
V().action.dur = 4;
V().action.useText = null;
V().action.actText = `Princess Bubblegum douses $B.target.name in a stinging, sticky syrup. They cough and stagger as their skin breaks out in welts.`;
V().action.act = justeffect('t',"Poisoned");
}
this.ready = false;
}
else if (act <= 50) {
var hitlist = [];
if (!V().enemies[0].dead && !V().enemies[0].stasis){
hitlist.push(V().enemies[0]);
} else if (!V().enemies[2].dead && !V().enemies[2].stasis){
hitlist.push(V().enemies[2]);
}
if (hitlist.length > 0) {
var t = random(0,hitlist.length-1);
target() = hitlist[t];
act = random(1,3)
switch (act) {
case 1:
V().action = new Action("Adrenaline");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"ATK Boost");
break;
case 2:
V().action = new Action("Stoneskin");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"DEF Boost");
break;
case 3:
V().action = new Action("Nootropic");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"SPC Boost");
break;
}
}
}
else if (this.cd.get("Chi Shield") < 0 && act <= 75 && act > 50) {
$.wiki('<<allytarget "buff">>');
V().action = new Action();
V().action.dur = 2;
V().action.useText = null;
V().action.actText = function () {
var str;
if (target() == subject()){
str = "becomes";
} else {
str = "$B.target.name is";
}
return `$B.subject.name presses a button, the air around ${str} surrounded by a force field.`
}
V().act = justeffect('t',"Chi Shield");
this.cd.set("Chi Shield",1);
}
else {
V().action = new Action();
V().action.useText = null;
V().action.actText = `Princess Bubblegum pulls something out of her pack.`;
V().act = `Something has changed...`;
this.ready = true;
}
} /* end loop */
return;
}
}}}
In contrast to Finn, Princess Bubblegum has a highly intelligent and pragmatic personality, so to be true to her character I wanted to give her more sophisticated behavior.
To begin with, you'll notice she has additional attributes above {{{actions}}}. Just like the player's abilities, we need some way to balance enemy abilities. Some abilities are very powerful, and would make the game way too hard if enemies could use them all the time! You could implement an energy system like the player characters use, but I found that too difficult. I used a simpler system: when the enemy uses a powerful attack, their "cooldown" variable for that attack is increased. Cooldown is reduced by 1 at the start of every turn, so enemies must wait that many turns before using a special attack again.
Cooldown is set with the {{{cooldown}}} property of an enemy entry, and you can evaluate if an action is usable with the function {{{CDcheck(<cooldown key>)}}}, which is common to all {{{Enemy}}} objects.
{{{
if (this.CDcheck("mass buff")) {
act = random(1,3)
switch (act) {
case 1:
V().action = new Action("Call to Arms");
break;
case 2:
V().action = new Action("Walled City");
break;
case 3:
V().action = new Action("Age of Enlightenment");
break;
}
this.cd.set("mass buff",2);
}
}}}
We can use {{{if}}} statements to make these variables influence Bubblegum's behavior. If her "mass buff" cooldown passes the check, she can choose this action; otherwise, she will skip it. This is the first branch of the if tree, so it gets the highest priority: she will <i>always</i> choose this action if the conditional is fulfilled.
Because there are three possible buffs for her to choose from, we have to add additional logic to select the specific action. We could make complicated logic here, such as prioritizing Defense if the party is injured or Special if she's already readied a bomb, but for simplicity's sake I just made the choice completely random. Bubblegum gets an even chance of selecting any buff ability, 1/3rd each.
(Like with Finn, since these are actions shared by puppets and therefore already defined in the database, we just need a single constructor call. We don't even need to modify the flavor text, since these skills don't have any to begin with.)
At the very end, we set the "mass buff" cooldown to 2. Because the check is for when the cooldown is <i>less than</i> 0, it will actually be 3 rounds before this action is available again. This is what we want, because it matches up with the duration of the buffs. The game could get pretty unfair if she could stack mass buffs, so we want to make sure she can only have one buff active at a time.
{{{
else if (this.ready) {
act = random(1,2)
if (act == 1) {
$.wiki('<<randomTarget>>');
V().action = new ItemAction("Grenade");
V().action.weight = 1;
V().action.useText = null;
V().action.actText = `Princess Bubblegum throws what appears to be a giant peppermint, but as soon as it hits the ground it explodes with the force of a grenade, spreading hard candy shrapnel everywhere.`;
}
else if (act == 2) {
$.wiki('<<randomTarget "ignore downed" "debuff">>');
V().action = new Action();
V().action.effweight = 0.8;
V().action.dur = 4;
V().action.useText = null;
V().action.actText = `Princess Bubblegum douses $B.target.name in a stinging, sticky syrup. They cough and stagger as their skin breaks out in welts.`;
V().action.act = justeffect('t',"Poisoned");
}
this.ready = false;
}
}}}
The very next branch checks against Bubblegum's {{{ready}}} attribute. If it's true, she's going to throw a bomb. Since this is the branch immediately after the last one, it takes priority over all other actions: she <i>will always</i> throw a bomb if she's ready and can't use a mass buff.
Once again, there are multiple specific actions she can take here, so we do another randomized roll. Half the time she will use a grenade, and half the time she will use a poisoning attack.
{{{
V().action = new ItemAction("Grenade");
V().action.weight = 1;
}}}
Notice that we call a pre-defined action here, but we proceed to specify a {{{weight}}} value anyway. In this case, I decided that the normal Grenade weight would be too unfair for this battle, so I lowered it to 1. Anything you specify here will overwrite anything that was defined by the constructor.
{{{
else if (act <= 50) {
var hitlist = [];
if (!V().enemies[0].dead && !V().enemies[0].stasis){
hitlist.push(V().enemies[0]);
} else if (!V().enemies[2].dead && !V().enemies[2].stasis){
hitlist.push(V().enemies[2]);
}
if (hitlist.length > 0) {
var t = random(0,hitlist.length-1);
target() = hitlist[t];
act = random(1,3)
switch (act) {
case 1:
V().action = new Action("Adrenaline");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"ATK Boost");
break;
case 2:
V().action = new Action("Stoneskin");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"DEF Boost");
break;
case 3:
V().action = new Action("Nootropic");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"SPC Boost");
break;
}
}
}
}}}
In the next branch we finally get into one of her "normal" actions: giving a buff drug to one of her allies.
{{{
if (act <= 50)
}}}
We see that the conditional uses the {{{act}}} variable. Because {{{act}}} was set to a random value between 1 and 100, this action will be selected half the time.
{{{
var hitlist = [];
if (!V().enemies[0].dead && !V().enemies[0].stasis){
hitlist.push(V().enemies[0]);
} else if (!V().enemies[2].dead && !V().enemies[2].stasis){
hitlist.push(V().enemies[2]);
}
if (hitlist.length > 0) {
var t = random(0,hitlist.length-1);
target() = hitlist[t];
}}}
We then proceed to implement some unique targeting logic, though it's similar to the logic discussed previously. Bubblegum will exclude herself as a viable target, and only choose between her allies (at index 0 and index 2). She won't target them if they're already defeated, of course, and since this is a buff, she won't target them if they're in Stasis either. The action definition is inside the final {{{if}}}, so this action will only be chosen if a viable target was found.
{{{
act = random(1,3)
switch (act) {
case 1:
V().action = new ItemAction("Adrenaline");
V().action.dur = 2;
V().action.effweight = 0.4;
V().act = justeffect('t',"ATK Boost");
break;
(etc.)
}}}
As with the mass buffs, Bubblegum has three to choose from, and once again the choice is made with a simple random roll. And as with the grenade, though the action mimics a database action, I have chosen to overwrite its values for game balance purposes.
{{{
else if (this.CDcheck("Chi Shield") && act <= 75 && act > 50) {
$.wiki('<<allytarget "buff">>');
if (target() !== null){
V().action = new Action();
V().action.dur = 2;
V().action.useText = null;
V().action.actText = function () {
var str;
if (target() == subject()){
str = "her becomes";
} else {
str = "$B.target.name is";
}
return `$B.subject.name presses a button, the air around ${str} surrounded by a force field.`
}
V().act = justeffect('t',"Chi Shield");
this.cd.set("Chi Shield",1);
}
}
}}}
Bubblegum's next action is another buff, this time providing Chi Shield.
{{{
if (this.cd.get("Chi Shield") < 0 && act <= 75 && act > 50)
}}}
This is another action with a cooldown, so we must check against it in the conditional. However, it must also pass an {{{act}}} range. Even with no cooldowns active, this action will only be selected when {{{act}}} is between 51 and 75, or 25% of the time.
{{{
$.wiki('<<allytarget "buff">>');
if (target() !== null){
}}}
Bubblegum can target herself with this ability, so we can use the regular {{{<<allytarget>>}}} widget. However, because this is done to find a buff recipient, it's possible that no target is selected (if everyone is in Stasis). We therefore need to check to make sure that our target isn't {{{null}}} before we proceed.
The action is otherwise quite straightforward. We create a blank action, define its attributes, and set the cooldown when we're done.
{{{
else {
V().action = new Action();
V().action.useText = null;
V().action.actText = `Princess Bubblegum pulls something out of her pack.`;
V().act = `Something has changed...`;
this.ready = true;
}
}}}
Bubblegum's final action is covered under a default {{{else}}} clause, so it will always trigger if no other condition is met. Effectively, this means it will trigger at {{{act}}} values 76-100, or 25% of the time. This action just sets {{{ready}}} to true, enabling Bubblegum to throw a bomb on the next turn.
Princess Bubblegum is my favorite character from <i>Adventure Time</i>, so I played favorites: I made her the focus of the battle and gave her two separate behaviors depending on what stage the battle was in. This has only described her normal behavior, but you can look up the "PB alone" case in {{{changeInto}}} to see how her behavior changes when the player defeats her cohorts.
<h4 id="enemyactions.bonnibel">Complex Example: Bonnibel</h4>
Princess Bubblegum reappears in the final battle of <i>Cartoon Battle</i>, but this time in her season 7 incarnation, where she has been stripped of her crown and forced to rely only on her own wits for survival. Not content to simply reuse her behavior from the first fight, I designed the most complex AI in the game for her, making her a "mirror" to the player's own Rogue.
{{{
"specialInit": function(actor) {
actor._idname = "PB Champ";
actor.name = "Bonnibel";
actor.ready = true;
actor.inventory = new Map([
["Bottled Chi",3],
["Adrenaline",3],
["Stoneskin",3],
["Nootropic",3],
["Powdered Glass",2],
["Grenade",2],
["Calamity Bomb",2],
["Gas Bomb",1],
["Flamethrower",1],
["Chaff Grenade",1],
["Panacea",3]
]);
actor.attackItemLogic = function () {
while (V().action === null) { // hasItem check should prevent infinite loop but BE CAREFUL
var act = random(1,100);
var noPowderedGlass = false;
var noCalamityBomb = false;
var count = 0;
if (act <= 40) { // use single-target item
console.log("Bonnibel single-target attack item branch");
chiCheck(["alert","dead"]);
console.log("Chi check performed, count = "+count);
if (count == V().puppets.length) {
noPowderedGlass = true; // if all puppets are alert or dead, there are no viable targets for Powdered Glass
}
chiCheck(["dead"]);
if (count == V().puppets.length) {
noCalamityBomb = true; // if all puppets are protected or dead, there are no viable targets for Calamity Bomb
}
if (noPowderedGlass && noCalamityBomb) {
act = 2;
} else if (noPowderedGlass) {
act = random(2,3);
} else if (noCalamityBomb) {
act = random(1,2);
} else {
act = random(1,3);
}
if (this.inventory.get("Powdered Glass") > 0 && act == 1) {
$.wiki('<<randomTarget "smart">>');
if (!target().alert && !target().chi && !target().stasis) {
V().action = new ItemAction("Powdered Glass");
action().actText = `Bonnibel pours what appears to be bright pink sugar onto her palm. She blows it into ${target().name}'s face, and they recoil and claw at their face as if they were shards of glass.`;
}
}
else if (this.inventory.get("Grenade") > 0 && act == 2) {
$.wiki('<<randomTarget "smart">>');
V().action = new ItemAction("Grenade");
action().act = splashDamage('p');
}
else if (this.inventory.get("Calamity Bomb") > 0 && act == 3) {
$.wiki('<<randomTarget "smart" "debuff">>');
V().action = new ItemAction("Calamity Bomb");
}
}
else { // use multi-target item
act = random(1,3);
if (this.inventory.get("Gas Bomb") > 0 && act == 1) {
V().action = new ItemAction("Gas Bomb");
V().action.act = massAttack('p',"Poisoned",V().action.dur);
}
else if (this.inventory.get("Flamethrower") > 0 && act == 2) {
V().action = new ItemAction("Flamethrower");
action().actText = `Bonnibel assembles a flamethrower from components on her tool belt, and douses your puppets in flame.`;
V().action.act = massAttack('p',"Burning",V().action.dur);
}
else if (this.inventory.get("Chaff Grenade") > 0 && act == 3) {
V().action = new ItemAction("Chaff Grenade");
V().action.weight = 1;
V().action.dur = 3;
V().action.actText = `Bonnibel lobs a grenade at you -- but to your surprise, when it explodes it leaves a massive cloud of shiny metal flakes. It just looks like silly confetti to you, but your puppets stutter and freeze up trying to see through all the flashing lights!`;
V().action.act = massAttack('p',"Dizzy");
}
}
}
}
},
"actions": function () {
while (V().action === null){
var act = random(1,100);
console.log("Bonnibel act = "+act);
var hasItem;
var stasisCount = 0;
var deadCount = 0;
enemies().forEach(function(enemy) {
if (enemy.stasis) {stasisCount++;}
if (enemy.dead) {deadCount++;}
});
/*
Rogue. Can use items freely, but has limited stock. Has 1 of each attack item + chaff grenade, puts rest of her points into drugs and Bottled Chi.
if SPC buffed, 75% chance to use attack item
normal:
50%: support
-first, check if anyone has >= 2 buffs and <= 1 ailment. If yes, they are added to a hitlist. If hitlist contains viable targets, 50% chance to use Stasis (target selection totally random). Otherwise...
-target is random, but (1-hp/maxhp) chance to reroll if they are below half health (don't waste items on doomed people)
-25%: bottled chi
-50%: buff drug for focus stat (Gumball gets 25% for all)
-12.5%: buff drug for other stat
20%: attack item
20%: off-balance one
10%/default: gun or reload
*/
this.inventory.forEach(function(stock,item) {
switch (item) {
case "Powdered Glass":
case "Grenade":
case "Calamity Bomb":
case "Flamethrower":
case "Gas Bomb":
case "Chaff Grenade":
if (stock > 0) {
hasItem = true;
break;
}
}
});
if (this.get("Special") > this.getBase("Special") && act <= 85 && hasItem === true) {
this.attackItemLogic();
this.inventory.inc(action().name,-1);
return;
}
act = random(1,100);
console.log("Special attack item check failed, new act = "+act);
if (stasisCount < (V().enemies.length - deadCount) && act <= 50) {
if (this.CDcheck("stasis")) {
var buffCount;
var ailmentCount;
var hitlist = [];
enemies().forEach(function(enemy) {
buffCount = 0;
ailmentCount = 0;
if (!enemy.stasis && !enemy.stunned){
enemy.effects.forEach(function(effect) {
if (effect.buff) {buffCount++;}
if (!effect.buff) {ailmentCount++;}
});
if (buffCount >= 2 && ailmentCount <= 1) {
hitlist.push(enemy);
}
}
});
if (hitlist.length > 0) {
act = random(0,hitlist.length-1);
V().B.target = hitlist[act];
V().action = new Action("Thaumastasis");
action().actText = `Bonnibel pulls a strange device out of her pocket, and points it at ${target().name}. There is an indescribable noise, and then the flow of magic around ${target().name} has frozen like amber.`;
this.cd.set("stasis",5);
return;
}
}
hasItem = false;
this.inventory.forEach(function(stock,item) {
switch (item) {
case "Bottled Chi":
case "Adrenaline":
case "Stoneskin":
case "Nootropic":
if (stock > 0) {
hasItem = true;
break;
}
}
});
if (hasItem) {
var keepGoing = true;
var chance;
while (keepGoing) {
$.wiki('<<allytarget "buff">>');
if (deadCount == 3) { // There is no point in rerolling if the user is the only viable target
keepGoing = false;
}
else if (target().name != "Bonnibel" && target().hp < (target().maxhp / 2)){ // Don't waste items on people who are about to die (but Bonnibel is a little selfish and excludes herself from this check)
chance = random(1,100);
if (chance < ((target().hp / target().maxhp) * 100)) {
keepGoing = false;
}
}
else {
keepGoing = false;
}
}
while (V().action === null) { // The hasItem check SHOULD prevent an infinite loop from occurring here, but be careful
act = random(1,8);
if (act <= 2 && this.inventory.get("Bottled Chi") > 0) {
V().action = new ItemAction("Bottled Chi");
}
else if (target().name == "Gumball") {
act = random(1,3);
switch (act) {
case 1:
if (this.inventory.get("Adrenaline") > 0) {V().action = new ItemAction("Adrenaline")};
break;
case 2:
if (this.inventory.get("Stoneskin") > 0) {V().action = new ItemAction("Stoneskin")};
break;
case 3:
if (this.inventory.get("Nootropic") > 0) {V().action = new ItemAction("Nootropic")};
break;
}
}
else {
switch (target().name) {
case "Bonnibel":
if (this.inventory.get("Nootropic") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Nootropic");
}
else if (this.inventory.get("Stoneskin") > 0 && act == 7) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Adrenaline") > 0 && act == 8) {
V().action = new ItemAction("Adrenaline");
}
break;
case "Dipper":
if (this.inventory.get("Adrenaline") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Adrenaline");
}
else if (this.inventory.get("Stoneskin") > 0 && act == 7) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Nootropic") > 0 && act == 8) {
V().action = new ItemAction("Nootropic");
}
if (V().B.dipper_drug_event != "done" && V().action !== null) {V().B.dipper_drug_event = "active";}
break;
case "Stevonnie":
if (this.inventory.get("Stoneskin") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Nootropic") > 0 && act == 7) {
V().action = new ItemAction("Nootropic");
}
else if (this.inventory.get("Adrenaline") > 0 && act == 8) {
V().action = new ItemAction("Adrenaline");
}
break;
}
}
}
this.inventory.inc(action().name,-1);
}
}
else if (act <= 80 && act > 50) {
this.attackItemLogic();
this.inventory.inc(action().name,-1);
}
else if (deadCount < 3 && act <= 90 && act > 80) { // no point in using this if she is only one left
$.wiki('<<randomTarget "ignore downed">>');
V().action = new Action("sonar");
action().useText = null;
action().actText = `Bonnibel pulls out a strange device and points it at ${target().name}. You hear a strange, high-pitched noise just on the edge of your hearing. It's easy enough for you to ignore, but ${target().name} can't seem to stand it -- they twitch and spasm, jerking backward and swaying.`;
action().act = justeffect('t',"Off-Balance",1);
}
else {
if (this.ready){
$.wiki('<<randomTarget "pierce">>');
V().action = new Action("shotgun");
action().weight = 1;
action().pierce = true;
action().useText = null;
action().actText = `Bonnibel blasts ${target().name} with her shotgun.`;
action().act = justdmg;
this.ready = false;
}
else {
V().action = new Action("load");
action().useText = null;
action().actText = `Bonnibel loads a cartridge into her shotgun.`;
action().act = null;
this.ready = true;
}
}
} /* end loop */
return;
}
}}}
That's a lot, yeah? Let's break it down.
{{{
actor.inventory = new Map([
["Bottled Chi",3],
["Adrenaline",3],
["Stoneskin",3],
["Nootropic",3],
["Powdered Glass",2],
["Grenade",2],
["Calamity Bomb",2],
["Gas Bomb",1],
["Flamethrower",1],
["Chaff Grenade",1],
["Panacea",3]
]);
}}}
To begin with, I gave her an actual inventory with limited item stocks, just like the player's. I avoided doing this for Bubblegum's first fight, instead giving her an infinite stock of items but requiring her to spend two turns to use them, because I feared it would be too complicated. However, I realized that if I wanted to truly mirror the player's own abilities (as is the intended theme of the final fight), I'd have to do it this way. It actually wasn't as difficult to implement as I feared; but we will get into that shortly.
We will revist {{{attackItemLogic}}} later. Let's look at the main {{{actions}}} function first.
{{{
var hasItem;
var stasisCount = 0;
var deadCount = 0;
enemies().forEach(function(enemy) {
if (enemy.stasis) {stasisCount++;}
if (enemy.dead) {deadCount++;}
});
}}}
The first thing you should notice is that we define additional variables here, not just the normal {{{act}}} randomizer. Because Bonnibel has a limited inventory stock, we need a variable that tells us whether or not she has the item she plans to use; and because she uses buff abilities, she needs to know if her allies have Stasis or are defeated -- if everyone is, there's no point in even considering using a buff.
{{{
this.inventory.forEach(function(stock,item) {
switch (item) {
case "Powdered Glass":
case "Grenade":
case "Calamity Bomb":
case "Flamethrower":
case "Gas Bomb":
case "Chaff Grenade":
if (stock > 0) {
hasItem = true;
break;
}
}
});
if (this.get("Special") > this.getBase("Special") && act <= 85 && hasItem === true) {
this.attackItemLogic();
this.inventory.inc(action().name,-1);
return;
}
}}}
Then we get into the actual action selection logic. However, our first check is a special circumstance that occurs outside the normal action tree.
Bonnibel's attack items are extremely valuable, so it makes sense that she should favor their use when she can get the most out of them; which is to say, when her Special stat is boosted. We can model this by giving her a higher chance of using an attack item than she would normally when {{{this.get("Special") > this.getBase("Special")}}}. However, this is hard to model within the same tree as other actions without interfering with the probabilities of those other actions. For instance, let's say a character takes one action at {{{act}}} values 0-30 and another at {{{act}}} values 31-50. Under a special circumstance, you want the character to take the first action 50% of the time instead. You could model this by telling them to perform the action {{{if (act <= 30 || (circumstance === true && act <= 50)}}}... but if you do not change the {{{act}}} range for the second action as well, now the character will <i>never</i> take that action if {{{circumstance}}} is {{{true}}}! All possible values for {{{act}}} that could trigger it will now trigger the first action instead, and so the second action will be ignored. In some cases, this may be what you want; but for most cases, it is easier to segregate these special actions away from the main {{{if}}} tree.
Additionally, because Bonnibel has a limited inventory, we need to check if she has any items that match with this action at all; otherwise, running through this logic will be at best a waste of time, and at worst a glitch that will allow her to keep using items after she's exhausted them. We check this with a simple {{{forEach}}} loop run over her {{{inventory}}} attribute; if any of her attack items have a {{{stock}}} value greater than 0, she can use an attack item, and so we set {{{hasItem}}} to {{{true}}}.
We've outsourced the actual details of this action to {{{attackItemLogic}}}, because the exact same logic will be used later in the tree. We will discuss it then.
{{{
act = random(1,100);
}}}
Finally, to avoid the crowding-out problem I just discussed, we have to reroll the {{{act}}} variable before entering the main tree; otherwise, no actions with {{{act}}} thresholds less than 85 would be possible!
{{{
if (stasisCount < (V().enemies.length - deadCount) && act <= 50)
}}}
We then enter Bonnibel's main action tree. Because I wanted Bonnibel to be more support-oriented, her support moves come first, and have a high probability of being used. However, because they're all buffs, it's worth checking if this branch is worth attempting at all. Remember, Stasis prevents the application of new effects, good or bad, so if every surviving character is in Stasis, buffs are useless. That's what we check with {{{stasisCount < (V().enemies.length - deadCount)}}}, using the variables we initialized at the beginning of the loop: this branch will only activate if the number of enemy characters with Stasis is less than the number of enemy characters remaining; i.e., if there are any viable targets for Bonnibel's buffs.
This branch actually has two sub-branches. Bonnibel can use one of her buff items, or she can use Witch's "Thaumastasis" ability to give someone Stasis. The latter is simpler, so that comes first:
{{{
if (this.CDcheck("stasis")) {
var buffCount;
var ailmentCount;
var hitlist = [];
enemies().forEach(function(enemy) {
buffCount = 0;
ailmentCount = 0;
if (!enemy.stasis && !enemy.stunned){
enemy.effects.forEach(function(effect) {
if (effect.buff) {buffCount++;}
if (!effect.buff) {ailmentCount++;}
});
if (buffCount >= 2 && ailmentCount <= 1) {
hitlist.push(enemy);
}
}
});
if (hitlist.length > 0) {
act = random(0,hitlist.length-1);
V().B.target = hitlist[act];
V().action = new Action("Thaumastasis");
action().actText = `Bonnibel pulls a strange device out of her pocket, and points it at ${target().name}. There is an indescribable noise, and then the flow of magic around ${target().name} has frozen like amber.`;
this.cd.set("stasis",5);
return;
}
}
}}}
Stasis is a risky buff to use, because it has potentially severe downsides: it prevents you from adding any other buffs for a long time, and any ailments the character has will be extended for the duration. For that reason, Bonnibel shouldn't just use it on anyone: we want her to check to make sure their number of buffs and ailments are within acceptable parameters. We can do this by running a {{{forEach}}} loop over the enemy party and tracking each character's number of buffs and ailments with {{{buffCount}}} and {{{ailmentCount}}}, respectively. If and only if the character has 2 or more buffs <i>and</i> 1 or fewer ailments, they become a viable target for Stasis and we add them to {{{hitlist}}}. (The character is not considered at all if they are Stunned, because that's too debilitating of an ailment to lock in Stasis no matter what.)
The likelihood of these things aligning is so rare that I did not include any other checks against Thaumastasis, such as an {{{act}}} randomizer -- if Bonnibel can use it, she will always use it.
If Bonnibel does not use Thaumastasis, she will move on to using one of her buff items.
{{{
hasItem = false;
this.inventory.forEach(function(stock,item) {
switch (item) {
case "Bottled Chi":
case "Adrenaline":
case "Stoneskin":
case "Nootropic":
if (stock > 0) {
hasItem = true;
break;
}
}
});
}}}
First, as with the attack items, we have to check to make sure she has any items to use in the first place. Since it's possible {{{hasItem}}} was flipped to {{{true}}} when checking for attack items, we have to reset it to {{{false}}} to avoid a false positive here. Otherwise, the check works exactly the same, with buff item names in place of attack item names.
Then things get complicated.
{{{
if (hasItem) {
var keepGoing = true;
var chance;
while (keepGoing) {
$.wiki('<<allytarget "buff">>');
if (deadCount == 3) { // There is no point in rerolling if the user is the only viable target
keepGoing = false;
}
else if (target().name != "Bonnibel" && target().hp < (target().maxhp / 2)){ // Don't waste items on people who are about to die (but Bonnibel is a little selfish and excludes herself from this check)
chance = random(1,100);
if (chance < ((target().hp / target().maxhp) * 100)) {
keepGoing = false;
}
}
else {
keepGoing = false;
}
}
}}}
Before anything else, Bonnibel has to pick a target. We already have a widget for this scenario, {{{<<allytarget>>}}}... but Bonnibel's targeting is a little more complicated. Because her items are limited in stock, she shouldn't throw them around willy-nilly: if someone looks likely to die before they can use the buff, she's not going to waste it on them. Cold, but efficient! The way I modeled this logic is thus: select a target as normal with {{{<<allytarget>>}}}, and then check if that target is below half HP. If they are, roll a random percentile value, and if it's greater than the percentage of HP they have remaining, roll for a new target. This effectively means that characters have a chance of being selected equal to the proportion of HP they have remaining; someone with 40% HP left still has a decent chance of getting a buff, but someone at 10% HP is almost certainly going to get passed over. This is accomplished through a {{{while}}} loop tied to the aptly-named Boolean {{{keepGoing}}}; Bonnibel will keep selecting new targets until we set {{{keepGoing}}} to {{{false}}}, which occurs if she is the only viable target, if the target is above half health, or the target is below half health but passes its luck test.
{{{
while (V().action === null) { // The hasItem check SHOULD prevent an infinite loop from occurring here, but be careful
act = random(1,8);
if (act <= 2 && this.inventory.get("Bottled Chi") > 0) {
V().action = new ItemAction("Bottled Chi");
}
else if (target().name == "Gumball") {
act = random(1,3);
switch (act) {
case 1:
if (this.inventory.get("Adrenaline") > 0) {V().action = new ItemAction("Adrenaline")};
break;
case 2:
if (this.inventory.get("Stoneskin") > 0) {V().action = new ItemAction("Stoneskin")};
break;
case 3:
if (this.inventory.get("Nootropic") > 0) {V().action = new ItemAction("Nootropic")};
break;
}
}
else {
switch (target().name) {
case "Bonnibel":
if (this.inventory.get("Nootropic") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Nootropic");
}
else if (this.inventory.get("Stoneskin") > 0 && act == 7) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Adrenaline") > 0 && act == 8) {
V().action = new ItemAction("Adrenaline");
}
break;
case "Dipper":
if (this.inventory.get("Adrenaline") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Adrenaline");
}
else if (this.inventory.get("Stoneskin") > 0 && act == 7) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Nootropic") > 0 && act == 8) {
V().action = new ItemAction("Nootropic");
}
if (V().B.dipper_drug_event != "done" && V().action !== null) {V().B.dipper_drug_event = "active";}
break;
case "Stevonnie":
if (this.inventory.get("Stoneskin") > 0 && act <= 6 && act > 2) {
V().action = new ItemAction("Stoneskin");
}
else if (this.inventory.get("Nootropic") > 0 && act == 7) {
V().action = new ItemAction("Nootropic");
}
else if (this.inventory.get("Adrenaline") > 0 && act == 8) {
V().action = new ItemAction("Adrenaline");
}
break;
}
}
}
}}}
Then it's time to pick which item Bonnibel uses. This is accomplished through a minature version of the greater action loop: Bonnibel will keep looping until she's selected an action. Note that this is why I was careful to exclude this branch entirely if she had no items to choose from; because this loop requires her to pick an item, if I didn't make that check, this could result in an infinite loop! We check to make sure she hasn't exhausted her stock with {{{this.inventory.get(<item name>) > 0}}} before each selection.
Bonnibel has four items to pick from: Bottled Chi, Adrenaline, Stoneskin, and Nootropics. However, I added a twist to make her seem a little more tactical: she'll play to characters' existing strengths. She has a higher chance to use Nootropics on herself, the item-user; a higher chance to use Adrenaline on Dipper, the damager; and a higher chance to use Stoneskin on Stevonnie, the tank. (Gumball, the jack-of-all-trades, has an even chance of getting any item.) Because of the fixed options here, I rolled the {{{act}}} randomizer from 1 to 8 instead of the normal percentile value. Bottled Chi gets a 1/4 chance, the "strong" buff a 1/2 chance, and the "weak" buffs get 1/8 each.
(As you can see in the inventory definition, I planned to give her status cure items as well. However, I decided designing logic for that was too much work and gave a general status cure ability to Dipper instead.)
{{{
this.inventory.inc(action().name,-1);
}}}
After the loop (meaning that an item was successfully selected), we decrement that item in the inventory so it's actually used up.
{{{
else if (act <= 80 && act > 50) {
this.attackItemLogic();
this.inventory.inc(action().name,-1);
}
}}}
Next is the normal branch for using an attack item. Normally, Bonnibel has only a 30% chance of using one, as opposed to an 85% chance when her Special stat is boosted. Because the same action occurs in two different places, I outsourced its logic to a function.
{{{
this.attackItemLogic = function () {
while (V().action === null) { // hasItem check should prevent infinite loop but BE CAREFUL
var act = random(1,100);
var noPowderedGlass = false;
var noCalamityBomb = false;
var count = 0;
if (act <= 40) { // use single-target item
console.log("Bonnibel single-target attack item branch");
chiCheck(["alert","dead"]);
console.log("Chi check performed, count = "+count);
if (count == V().puppets.length) {
noPowderedGlass = true; // if all puppets are alert or dead, there are no viable targets for Powdered Glass
}
chiCheck(["dead"]);
if (count == V().puppets.length) {
noCalamityBomb = true; // if all puppets are protected or dead, there are no viable targets for Calamity Bomb
}
if (noPowderedGlass && noCalamityBomb) {
act = 2;
} else if (noPowderedGlass) {
act = random(2,3);
} else if (noCalamityBomb) {
act = random(1,2);
} else {
act = random(1,3);
}
if (this.inventory.get("Powdered Glass") > 0 && act == 1) {
$.wiki('<<randomTarget "smart">>');
if (!target().alert && !target().chi && !target().stasis) {
V().action = new ItemAction("Powdered Glass");
action().actText = `Bonnibel pours what appears to be bright pink sugar onto her palm. She blows it into ${target().name}'s face, and they recoil and claw at their face as if they were shards of glass.`;
}
}
else if (this.inventory.get("Grenade") > 0 && act == 2) {
$.wiki('<<randomTarget "smart">>');
V().action = new ItemAction("Grenade");
action().act = splashDamage('p');
}
else if (this.inventory.get("Calamity Bomb") > 0 && act == 3) {
$.wiki('<<randomTarget "smart" "debuff">>');
V().action = new ItemAction("Calamity Bomb");
}
}
else { // use multi-target item
act = random(1,3);
if (this.inventory.get("Gas Bomb") > 0 && act == 1) {
V().action = new ItemAction("Gas Bomb");
V().action.act = massAttack('p',"Poisoned",V().action.dur);
}
else if (this.inventory.get("Flamethrower") > 0 && act == 2) {
V().action = new ItemAction("Flamethrower");
action().actText = `Bonnibel assembles a flamethrower from components on her tool belt, and douses your puppets in flame.`;
V().action.act = massAttack('p',"Burning",V().action.dur);
}
else if (this.inventory.get("Chaff Grenade") > 0 && act == 3) {
V().action = new ItemAction("Chaff Grenade");
V().action.weight = 1;
V().action.dur = 3;
V().action.actText = `Bonnibel lobs a grenade at you -- but to your surprise, when it explodes it leaves a massive cloud of shiny metal flakes. It just looks like silly confetti to you, but your puppets stutter and freeze up trying to see through all the flashing lights!`;
V().action.act = massAttack('p',"Dizzy");
}
}
}
}
}}}
This logic is similar to the support item logic. It's segregated into single-target items (Powdered Glass, Calamity Bombs, and Grenades) and items that don't require a target (Gas Bombs, Flamethrowers, and Chaff Grenades). There's some additional checks for Powdered Glass and Calamity Bombs using the {{{chiCheck}}} function; if all possible targets are Alert, there's no point in even considering the former, and likewise, there's no point in considering the latter if all possible targets have Chi Shield or Stasis. If either of those cases turn out to be true, they're removed as a possibility for selection.
Bonnibel's other abilities are normal and should be straightforward to follow.
Your takeaway from this example should be that <b>supportive AI is hard!</b> Just dealing damage is simple, but knowing when and how to strategically apply a buff is quite complicated -- even something as simple as personalizing buffs to certain characters required a huge {{{switch}}} statement. It's worthwhile to step back and analyze your own decisions when playing RPGs -- what goes through your mind when you decide who to pick for a buff or heal? What variables influce your decision? Can you think of how to translate your reasoning into code? It can be quite hard to account for every possibility!
These examples should hopefully show you how to implement enemy behavior yourself. AI design is unfortunately not my forte, so many of these examples are relatively simplistic. If you ask around RPG design communities, they may be able to help you with more complex designs.
<h2 id="encounters">Creating Encounters</h2>
Once you've created your enemies, you still have to bundle them into an <i>encounter</i>. An encounter is a specific battle containing a party of enemies. Each one may correspond to, for example, a particular room of a dungeon, or to one of several possible random encounters.
Encounters are defined in the {{{database-encounters.tw}}} file in the {{{passages-custom}}} folder. In this file, you'll see a widget called {{{<<callEncounter>>}}} that contains a {{{<<switch>>}}} statement keyed to the variable {{{$scenario}}}. Create a case statement for your scenario id and define your {{{$enemies}}} party variable within it. You can also add other variables, such as making the encounter an ambush or defining an aesthetic style. (Any string you assign to the {{{$B.style}}} property will be added to the passage body as a class for the duration of the battle.)
There is another widget defined here, {{{<<populateEnemies>>}}}, to make your work a little easier. Instead of having to write {{{new Enemy}}} for every single enemy, just pass their names to {{{<<populateEnemies>>}}} and it'll automate that for you. Note that you will have to wrap the argument in backticks {{{`}}} for it to be read correctly.
To start an encounter in-game, you can set the {{{$scenario}}} manually and make a direct link to the "Preparation" passage, or you can use the {{{<<fight>>}}} widget.
{{{
<<widget "fight">>
<<if $args.length > 0>>
<<if typeof($args[1]) == "string">>
<<set _text = $args[1]>>
<<else>>
<<set _text = "BATTLE">>
<</if>>
<center><<button _text "Preparation">><<set $scenario = $args[0]>><</button>></center>
<<else>>
<b>ERROR in fight: no arguments</b>
<</if>>
<</widget>>
}}}
This widget provides a quick and standardized format for battle links. Pass the name of the encounter you want to call as the first argument, and custom text for the battle button as the second. By default, the button text will default to "BATTLE". The widget will create a centered button that forwards the player to the battle, all with just one widget call.
<h2 id="customization">Customization</h2>
Another RPG Engine is designed to be modular and to allow the user to customize features to suit their needs. This is handled through the passages stored in the {{{passages-custom}}} folder, which are slotted into core passages when needed.
<h3 id="custom.init">Initial Variables</h3>
<code>StoryInit</code> is a special passage that is run when the game starts. It is used to define many variables that determine several aspects of the engine, such as the color of health bars, the constants used in damage formulas, and the elements used for magic spells. These variables are set to certain defaults, but you can overwrite them with new specifications by editing {{{user storyinit.tw}}}.
Most variables are explained in the comments of the <code>StoryInit</code> file, but this section will go into detail on some of the more complex ones:
* {{{formula}}} determines the default damage formula used for attacks. A few options are provided by default. See [[Design]] for more information.
* {{{statInfo}}} provides a tooltip description of every stat when the player hovers over its name in the party menu. This is helpful for communicating to your player what each stat does, especially if you use particularly complex or esoteric stats.
** {{{statInfo}}} is also considered a database of every stat used in the game, so make sure it matches the stats you define for characters.
* {{{hiddenStats}}} is an array of stat names that will be hidden in places where stats are normally displayed, such as the party menu.
* {{{ELEMENT_LIST}}} is an array of names corresponding to all the elemental properties that will be used in your game.
** {{{elementMessages}}} is an object containing messages that will be added to damage reports to show that an attack has hit an elemental weakness or resistance.
* {{{MENU_OPTIONS}}} is an array of strings corresponding to what options the player can select in the party menu. For the options available in the default engine, see the "party menu" twee files. Additional options you will have to create yourself.
* {{{STATUS_SCREENS}}} is an object of arrays of strings corresponding to what options the player can select in status panes. There are separate lists for the status menu, the in-battle status pane, and the bestiary. Additional options you will have to create yourself.
* {{{DEFAULT_EQUIP_SLOTS}}} is an object that will be read to generate default equipment slots if no {{{equipSlots}}} object is provided in the character database entry. Keys correspond to equipment slots, values correspond to the number of subslots.
* {{{THREAT_TARGETING}}} and {{{BATTLE_GRID}}} are Booleans that allow you to activate alternate gameplay styles. See [[Documentation (Advanced)]] for details on threat targeting and [[Additional Features]] for details on the battle grid.
* The various animation variables determine the length of the battle popup animations, in milliseconds.
This is also where you should define the player's starting party with the {{{$puppets}}} variable, and their starting inventory with the {{{$inventory}}} variable.
<h3 id="custom.passages">Customizing Passages</h3>
The passages in {{{passages-custom}}} are slotted into certain core passages through the {{{<<include>>}}} macro. By modifying these custom passages, you can customize various features without needing to alter the core passages.
* {{{battle display mods actorlist}}} lets you conditionally add classes to the enemy or puppet displays in the battle layout, such as reversing the display order for certain encounters.
* {{{battle interruptions}}} will appear during the action phase, after the action's {{{useText}}} but before its {{{actText}}}. You can make them appear conditionally based on encounter scenario or other variables. The interruptions for the Steven Universe encounter are provided as an example.
* {{{custom battle preparation}}} is run at the end of the setup code before every battle. By default, it only adds the {{{<<restock>>}}} widget, which refreshes any items used during the battle.
* {{{custom end of action effects}}} is run on every action, after followup attacks and potential surrender failure. Use it for any special action adjustments you want to make. By default, this only includes the EN refund for the "Focus" ability.
* {{{custom end of battle}}} is run at the end of the {{{<<endofbattle>>}}} widget. By default, it only adds the {{{<<restock>>}}} widget, which refreshes any items used during the battle.
* {{{custom newturn}}} is run at the end of the {{{<<newTurn>>}}} widget. By default, it regenerates puppet EN.
* {{{damagecalc custom factors}}} is run at the end of {{{<<damageCalc>>}}}, the widget that runs damage calculation. Use it for any miscellaneous modifiers you want to apply to damage, such as the multipliers from Fighter's "Defender" and "Berserker" effects.
* {{{effect adder custom blocks}}} runs logic for protective effects, such as Chi Shield.
* The various {{{formula}}} passages let you customize the formulas for various calculations, such as accuracy, critical rate, and damage.
* {{{loss of control effects}}} handles the logic for loss-of-control effects, and is run at the start of every player turn. The default passage includes logic for the "Hatred", "Confusion", and "Charmed" effects, but you can add additional branches for your own effects.
* {{{special battle lines}}} is displayed in-between the enemy and player parties in the battle screen. This is useful for displaying relevant in-battle messages, such as the progress for Mage's sunrise spell in the Marceline battle.
* {{{special checks}}} is run at the start of the player turn, and is used to forward the player to special scenes, such as Dipper and Mabel's transformations, or to make other adjustments. By default, it includes the handler for loss-of-control effects.
* {{{user storyinit}}} is appended to {{{StoryInit}}}, as previously discussed.
Additionally, if you create a passage file with the same name as an existing passage, it will be overwritten when your game is compiled. This allows you to overwrite the default widgets and passages completely with your own versions if you need to radically change things -- but make sure you know what you're doing!Use the table of contents to navigate. You can come back to the top at any time by pressing the "home" key on your keyboard.
This documentation will assume you are familiar with the explanations detailed in [[Documentation (Basic)]]. This advanced documentation will go into detail on the code of every passage.
In addition to the code in the engine itself, the engine uses <a href="https://twinelab.net/custom-macros-for-sugarcube-2/" target="_blank">Chapel's custom macros</a>. Click the link to learn more.
The opening sections will be quite technical, and deal with external script. If you'd like to skip to how the Twine passages operate, you can head to <a class="noExternal" href="#setup">Setting up the battle</a>.
<h1>Table of Contents</h1>
><a href="#JS">JavaScript vs. Twine</a>
>><a href="#JS.config">Config</a>
>><a href="#JS.databases">Objects, Classes, and Databases</a>
>><a href="#JS.actor">Actor Class</a>
>>><a href="#JS.actor.constructor">Constructor</a>
>>><a href="#JS.actor.getters">Getters and Setters</a>
>>><a href="#JS.actor.effectFunctions">Effect Functions</a>
>>><a href="#JS.actor.equipFunctions">Equipment Functions</a>
>>><a href="#JS.actor.toleranceFunctions">Tolerance Functions</a>
>>><a href="#JS.actor.misc">Miscellaneous Functions</a>
>><a href="#JS.stats">Stat Class</a>
>>><a href="#JS.stats.fill">The FillStat Class</a>
>>><a href="#JS.tolerances">Tolerance Class</a>
>><a href="#JS.puppets">Puppet Class</a>
>><a href="#JS.enemies">Enemy Class</a>
>><a href="#JS.actions">Action Class</a>
>><a href="#JS.items">Inventory and Items</a>
>><a href="#JS.support">Support Functions</a>
>><a href="#JS.serial">Serialization and Save Files</a>
>><a href="#JS.updates">What if we want to update something in a new version?</a>
><a href="#display">Display and aesthetics</a>
>><a href="#statusdisplay">Displaying the status pane</a>
>><a href="#nobr">The nobr tag</a>
>><a href="#pronouns">Personalized text</a>
>><a href="#healthbars">Health Bar Formatting</a>
>><a href="#battlestyle">Special Aesthetics for Battles</a>
>><a href="#savesmodifier">Modifying the Save Display</a>
>><a href="#animationsDoc">Animations</a>
>>><a href="#animations.design">Designing the Animations</a>
>>><a href="#animations.setup">Setup</a>
>>><a href="#animations.execution">Execution</a>
>>><a href="#animations.disabling">Disabling the Continue Button</a>
><a href="#setup">Setting up the battle</a>
><a href="#skeleton">How the battle passages work</a>
>><a href="#auto-end">Auto-endturn</a>
>><a href="#actorlistDoc">Actor List</a>
>>><a href="#actorBoxDoc">Actor Box</a>
>><a href="#commands">Commands</a>
>><a href="#targetingphase">Targeting Phase</a>
>><a href="#confirmphase">Confirm Phase</a>
>><a href="#spellphase">Spell Phase</a>
>><a href="#actionphase">Action Phase</a>
>><a href="#actionqueue">The Action Queue</a>
>><a href="#endOfRound">End of Round</a>
>><a href="#enemyphase">Enemy Phase</a>
>>><a href="#enemyphase.multiActions">Enemies with Multiple Actions</a>
>><a href="#advanceturn">Advancing Turns</a>
>><a href="#victorydefeat">Victory and Defeat</a>
><a href="#actions">Action Mechanics</a>
>><a href="#actions1">Special Cases</a>
>><a href="#actions2">The Functional Link</a>
>><a href="#actions3">Info Display</a>
>><a href="#crossbow">How does Rogue's crossbow work?</a>
>><a href="#items">Items</a>
>><a href="#delayed">Delayed Attacks</a>
><a href="#damage">Calculating and applying damage</a>
>><a href="#damage.1">Calculating damage</a>
>><a href="#damage.2">Applying damage</a>
>>><a href="#dmgreflection">Damage Reflection</a>
>>><a href="#dmg.onhit">onHit</a>
>>><a href="#dmg.shock">Shock cures</a>
>>><a href="#dmg.counters">Setting up counterattacks</a>
><a href="#effects">Status Effects</a>
>><a href="#effects1">Applying effects</a>
>>><a href="#effects1.1">Accounting for protective effects</a>
>>><a href="#effects1.2">Numerical components</a>
>>><a href="#effects1.3">Stackability</a>
>><a href="#effects.calc">Calculating numerical effects</a>
>>><a href="#effects.calc.mods">Stat mods</a>
>>><a href="#effects.calc.dot">Damage-over-time effects</a>
>><a href="#effects.loss">Loss-of-control Effects</a>
><a href="#targeting">Targeting and Enemy AI</a>
>><a href="#targeting.basic">Standard Targeting</a>
>>><a href="#targeting.basic.1">Martyr check</a>
>>><a href="#targeting.basic.2">Reactive enemies</a>
>>><a href="#targeting.basic.untargetable">Untargetability</a>
>>><a href="#targeting.basic.3">Mercy</a>
>>><a href="#targeting.basic.4">Smart targeting</a>
>>><a href="#targeting.basic.5">Normal targeting</a>
>>><a href="#targeting.basic.7">Protection check</a>
>>><a href="#targeting.basic.puppets">Puppet Targeting</a>
>><a href="#targeting.threat">Threat-based/Aggro Targeting</a>
>>><a href="#targeting.threat.1">Setup</a>
>>><a href="#targeting.threat.2">Logic</a>
>><a href="#dispel">Dispel Targeting</a>
>>><a href="#dispel.mass">Mass dispels</a>
>><a href="#targeting.ally">Ally Targeting</a>
><a href="#menus">Menu Functionality</a>
>><a href="#menus.status">Status</a>
>><a href="#menus.inventory">Inventory</a>
>><a href="#partypicker">Party Picker</a>
>><a href="#equipmanager">Equipment Manager</a>
>><a href="#itemshop">Item Shop</a>
><a href="#hotkeys">Hotkeys</a>
>><a href="#hotkeys.summary">Summary</a>
><a href="#widgets">Other Widgets</a>
>><a href="#chain">The {{{<<chain>>}}} widget</a>
>><a href="#refreshPuppets">{{{<<refreshPuppets>>}}}</a>
>><a href="#deathcheck">{{{<<deathcheck>>}}}</a>
>><a href="#statusDoc">{{{<<status>>}}}</a>
>><a href="#endofbattle">{{{<<endofbattle>>}}}</a>
>><a href="#backbtn">{{{<<backbtn>>}}}</a>
>><a href="#victorycheck">{{{<<victorycheck>>}}}</a>
>><a href="#stat">{{{<<stat>>}}}</a>
>><a href="#itemdrop">{{{<<itemDrop>>}}}</a>
>><a href="#musicwidgets">Music widgets</a>
>><a href="#find">{{{<<find>>}}}</a>
<h2 id="JS">JavaScript vs. Twine</h2>
To begin, it's important you understand how to operate JavaScript in Twine.
The code you see in Twine passages, such as {{{<<if condition>><<set $variable to value>>}}}, is a modified form of JavaScript designed to be easier to use with stories. However, it has some limitations. For a project as complex as an RPG engine, I chose to include pure JavaScript as well.
Fortunately, Twine is compatible with external JavaScript files. Normally, this is accessed through a special passage that can be accessed by clicking the story's title at the bottom of the screen; however, because Twine only allows one JavaScript passage, for ease of use, I separated it into multiple independent JavaScript files which can be found in {{{src/javascript}}}. For this engine, the JavaScript is mostly used for storing information for data structures used in the game, such as characters, items, and actions. You can edit these files by opening them in a text editor such as Notepad++, and add them to the story by compiling them with Tweego. (Editing through Twine is unfortunately difficult, due to a lack of a search feature.)
JavaScript is very similar to SugarCube Twine code, but has a separate store of variables from the ones defined in Twine. To access a story variable in JavaScript, you must put {{{variables().}}} in place of where you would put the {{{$}}} symbol in Twine. To access a temporary variable, you must put {{{temporary().}}} in place of the {{{_}}} symbol.
<h3 id="JS.config">Config</h3>
The first JavaScript file is "0_config". The 0 is added to ensure it comes first in the directory, as files are compiled in alphabetical order.
This file stores configuration settings for Twine, such as how saves are formatted and what path to use for media files. Additionally, the {{{variables()}}} function used to access story variables is assigned to the shorter name {{{V()}}}, for convenience. Note that this is case sensitive.
You can add additional configuration settings as needed for your story. See the SugarCube documentation for details.
<h3 id="JS.databases">Classes and Databases, advanced</h3>
In [[Documentation (Basic)]], you learned how to create your own entries for the engine's databases. Here, you will learn how those entries are read and constructed into live objects.
You also learned that objects are collections of sub-variables called <b>properties</b> that you can access with the dot operator. Typically, there are variables stored inside each property; however, that is not strictly necessary.
RPGs use a lot of terms and structures that have associated data, such as characters, abilities, and items. In many cases, the data associated with these structures doesn't change. For instance, Fighter's "Sword" ability always has a cost of 2, a weight of 1, always does damage for its action, and always has the same info and description data. None of that will change over the course of the game.
We therefore don't need to encode all of that data into a "Sword" object. In fact, it would be very good if we didn't have to, because Twine has to copy every single object and variable every time it makes a passage transition! With lots of bulky objects, that can really slow down performance. Instead, since the data doesn't change, we can define it in a separate database object, and look it up through functions tied to object classes.
Here is an example from the <code>Action</code> class:
{{{
get actionData () {
return (setup.actionData[this.name] || setup.actionData[this.displayname] || {});
}
}}}
This is known as a <b>getter function</b>. When you define a function through the keyword <code>get</code>, that function can be called just like an attribute of the object -- no function call required! This means it can't take any arguments, but that's okay. We only need simple functions for this.
With this function, if we define an object in <code>setup.actionData</code> with the action's name as a property, this will return whatever data is associated with that action. (Note the parentheses and <code>||</code> operator; if you are not familiar with how that feature works, read about it <a href="https://stackoverflow.com/questions/2802055/what-does-the-construct-x-x-y-mean" target="_blank" rel="nofollow">here</a>. tl;dr If the result of the code is falsy, it will skip it and attempt to return the next entry. This is useful for defining default values in the case that something is undefined.)
We can then define a getter for every property we need by extracting it from <code>actionData</code>:
{{{
get cost () {
var val = this._cost;
if (val === undefined) {
val = this.actionData.cost;
}
if (val === undefined) {
val = 0;
}
return (val instanceof Function) ? val(this) : val;
}
(etc.)
}}}
Note the way this is constructed. There may be times where we <i>do</i> want to modify a particular Action instance with a unique property that's different from its property in the database, such as with the variable effects of Mage's spells. For this, we use a special property with the same name, but prepended with an underscore <code>_</code> to denote it as an override property. We may also want to calculate the property through another function rather than hold it as a single value, so we have to include a check to call the function if there is one.
(Why does this look so much more complicated than the one-line return statement in the last getter? Because variable return works on logical operation, which means it skips an entry if it evaluates to <code>false</code> -- <i>not just</i> if it's <code>undefined</code>! <code>cost</code> can be 0, which is a falsy value. Because of this, we have to write out a full <code>if</code> tree that explicitly only excludes <code>undefined</code> values. You'll want to do this for any property that could have a falsy evaluation, which are listed <a href="https://developer.mozilla.org/en-US/docs/Glossary/falsy" target="_blank" rel="nofollow">here</a>. It's a pain, but doing it here will save you more trouble in the long run!)
This method of offloading constant data to a database and accessing it through lookup is known as <b>Flyweight</b>. It is very useful for streamlining our data: Now, our objects only have to store a <code>name</code> property and any dynamic properties that might change over the course of the game. As an added bonus, this makes updating the game between versions easier as well: The <code>setup</code> variable exists independent of save states, so any changes made to the database will automatically be reflected in existing saves.
Now that you know the general principles, let's get into the individual classes.
<h3 id="JS.actor">Actor Class</h3>
The <b>Actor</b> class defines a character who, as the name implies, acts in battle. This means it should define all the attributes we want battling characters to have.
<h4 id="JS.actor.constructor">Constructor</h4>
All classes require a <b>constructor</b> function that standardizes how they are initialized. It takes arguments just like any other function that are used in the construction of the object. Since we use a database structure, for {{{Actors}}}, we only need the name.
{{{
if (this instanceof Puppet) {
this.id = "p";
} else if (this instanceof Enemy) {
this.id = "e";
} else {
this.id = "_";
}
this.id += (new Date().getTime() + Math.random() * 0x10000000000).toString(16);
}}}
The first thing we do in the constructor is create a unique ID value for the Actor. This is necessary for a few other features that need to be able to identify a specific Actor out of an array. It's also necessary for the ID to distinguish between Puppets (player characters) and Enemies, so we use an if tree to append a unique symbol to the start of the ID depending on which one this object is. (Puppet and Enemy are also object classes; they will be explained later.)
{{{
this.name = name;
}}}
We then start assigning values to the object's attributes, starting by assigning the <code>name</code> argument to a <code>name</code> property.
{{{
if (this.data)
}}}
Then we perform this check before proceeding. We will learn how this works when we examine Actor's functions. For now, just remember this.
{{{
this._HPregen = {
"flat": new Stat(0),
"percent": new Stat(0)
}
this.elements = new Map();
if (setup.ELEMENT_LIST !== undefined){
setup.ELEMENT_LIST.forEach(function(x) {
this.elements.set(x,{"percent": new Stat(1), "flat": new Stat(0)});
}, this);
}
this.tolerances = new Map();
if (setup.effectData !== undefined){
Object.keys(setup.effectData).forEach(function(x) {
this.tolerances.set(x,new Tolerance(0));
}, this);
}
}}}
Then three properties are initialized to default valies: HP regeneration (which contains both flat and percentage rates), elemental affinities, and ailment tolerances. {{{ELEMENT_LIST}}} is a story variable you can define in StoryInit to list all the elements you want in your game. This code checks to make sure it exists, and if it does, runs across it to create elemental affinity values for each. (By default, they are all neutral: 1 for the proportional multiplier and 0 for flat reduction.) This will prevent the game from throwing an error when a character is attacked with an elemental ability; if the game tries to read a value and there's nothing there, it may create problems. Similarly, {{{tolerances}}} generates ailment resistances by running over {{{effectData}}}, the database of all status effects in your game (see <a class="noExternal" href="#effects">Status Effects</a>), and creating resistances based on the name values it extracts.
The {{{elements}}} and {{{tolerances}}} attributes are defined as a Map object rather than an array. Maps are very similar to arrays, but their index values can be anything, not just numbers. To extract a value from a Map, you use {{{<var>.get(<key>)}}}, similarly to how you use {{{<var>[index]}}} to extract values from arrays. This makes it much easier to call an affinity in response to an elemental attack, as you can just input the name and the program will pull up the corresponding value, instead of having to remember the order of the array.
{{{
this._maxhp = new Stat(this.data.hp);
this._hp = this.data.hp;
this.stats = {};
for (let [pn,v] of Object.entries(this.data.stats)) {
this.stats[pn] = new Stat(v);
}
if (this.data.elements) { this.setElements(Object.entries(this.data.elements),"percent"); }
if (this.data.tolerances) { this.setTol(Object.entries(this.data.tolerances)); }
if (this.data.retaliations) { this._retaliations = new FillStat(this.data.retaliations); } else { this._retaliations = new FillStat(0); }
}}}
Next we start actually extracting data from the database. Main stats such as Attack and Defense are stored in the <code>stats</code> attribute, which is populated by looping over the entries provided by the <code>stats</code> property in the character's database entry. (This means that the initialization will work for any number of stats; you don't need to change the code here if you want to add or remove stats.) If the character has defined <code>elements</code>, <code>tolerances</code>, or <code>retaliations</code> in their database entry, those are extracted; otherwise, they remain at their default values. (Currently, only percent-based elemental affinities are initialized here. You'll have to implement your own code for flat rates.)
{{{
this.isDone = false;
this.dead = false;
this.effects = [];
this._deathMessage = `${this.name} is defeated!`;
}}}
Next we initialize some important flags. <code>isDone</code> will be used to determine if a character has finished their turn, and <code>effects</code> holds status effects. After this, we set a bunch of flags that mark if a character has a certain status effect (see <a class="noExternal" href="#effects">Status Effects</a>).
{{{
this.equipment = new Map([
["Weapon",null],
["Armor",null],
["Accessory",[null,null] ]
]);
}}}
Finally, we define equipment slots. Thesse are discussed more in <a class="noExternal" href="#JS.items">the Item Database</a>.
{{{
else {
console.log("ERROR: Actor "+name+" is not in database");
this.name = "INVALID ACTOR";
this._hp = 1;
this._maxhp = new Stat(1);
}
}}}
We finish with a default error handler in the case that our earlier <code>if (this.data)</code> check failed. This will at least fill the important fields so we don't have a completely undefined object breaking things, and log an error to the database that tells us what happened.
<h4 id="JS.actor.getters">Getters and Setters</h4>
We are then finished with the constructor, but we can continue to add other functions. These functions, called <b>object methods</b>, can be used by any instance of the Actor class.
{{{
get data () {
var id = (this.idname || this.name);
if (this instanceof Puppet) {
return (setup.puppetData[id] || false);
}
else if (this instanceof Enemy) {
return (setup.enemyData[id] || false);
}
else {
return undefined;
}
}
}}}
Like all database-associated objects, we need to start with a function that accesses the database. This one is a little more complicated than others, because we don't intend to use pure Actor objects in our game; every Actor is going to be one of two subclasses, Puppets or Enemies. The data for these classes are defined in separate databases, so we need to check the type of our object to determine which database to access. We also need to return <code>false</code> for our default value instead of an empty object so that our <code>if (this.data)</code> check will fail in the event there's no matching database entry.
(We also use a multiple-choice assignment to pick from two possible identifiers, for <a class="noExternal" href="#JS.enemies">reasons</a>.)
We next define getters and setters for several properties. Accessing Map values is a little unwieldy, especially when those values are themselves objects, so we define functions with shorter names that do the same thing. We have functions here for getting and setting all modifiable stats, and obtaining equipment and circumstance bonuses.
Note the <code>hp</code> setter:
{{{
set hp (amt) {
this._hp = Math.clamp(amt,0,this.maxhp);
}
}}}
This prevents a character's HP from going below 0 or above their maximum HP.
{{{
get (key) {
// calculates effective stats
if (this.stats[key]) {
let v = this.stats[key];
let n = v.current;
if (n < setup.MIN_STAT && !(key == "Defense" && this.forsaken)){
n = setup.MIN_STAT; // 0 in subtractive, 1 otherwise
}
return Math.round(n);
} else {
console.log("ERROR in stat getter, target does not have requested stat");
return 0;
}
}
}}}
Similarly, this function (just called <code>get</code>) not only returns a stat, but ensures it stays within acceptable bounds. In a divisive defense system, letting a defense stat hit 0 can result in a division by zero error, so this is very important! (This isn't an issue in the default subtractive system, so I included a little handler here that lets Defense go negative if the character has the Forsaken status. This is how Forsaken is uniquely able to reduce Defense below 0.)
{{{
getElement (needle,type) {
if (type === undefined) {
return this.elements.get(needle);
} else {
return this.elements.get(needle)[type].current
}
}
setElements (array,type) {
// Easy way to set base elemental affinities. Takes an array of arrays that each contain two elements, the name of the effect and the base value. Pass the second argument to denote if you are setting flat or percent rates.
if (type === undefined || typeof(type) !== 'string') {
console.log("ERROR in setElements: no type defined");
} else {
array.forEach(function(data) {
if (this.elements.has(data[0])) {
this.elements.get(data[0])[type].base = data[1];
}
}, this);
}
}
(...)
getTol (key) {
// This is for evaluating tolerance in-battle, and therefore only returns the current value. DON'T use it to access the whole tolerance object, because that's not what it returns.
if (!this.tolerances.has(key)){
return undefined;
} else {
return this.tolerances.get(key).currentVal;
}
}
setTol (array) {
// Easy way to set base tolerances. Takes an array of arrays that each contain two elements, the name of the effect and the base value.
array.forEach(function(data) {
if (this.tolerances.has(data[0])) {
this.tolerances.get(data[0]).base = data[1];
}
}, this);
}
}}}
We also have some special getters and setters for handling elemental affinities and ailment tolerances. Because these are complicated structures stored in large maps, it's difficult to access and modify them directly. {{{getElement}}} lets you specify if you want the flat soak or the proportional resistance associated with that element and returns the {{{current}}} value (see <a class="noExternal" href="#JS.stats>the Stat class</a>) of the associated property, making it very easy to plug it into in-game calculations; {{{getTol}}} does the same for tolerances, but is less complicated since tolerances don't have sub-properties for flat and proportional. (Note that these are not normal "getter" functions, as they do not actually access the maps' raw values. You will need to use {{{.get(key)}}} for that.) {{{setElements}}} lets you quickly set base values for elemental affinities, and {{{setTol}}} does the same for tolerances.
<h4 id="JS.actor.effectFunctions">Effect Functions</h4>
{{{
addEffect (effect,time,power) {
if (typeof(effect) == 'string'){
effect = new Effect(effect,time,power);
}
this.effects.push(effect);
effect.onApply(this);
return effect.addText(this.name);
}
removeEffect (effect,mods) {
mods = (mods || {});
if (!this.stasis || mods.pierce === true){
var E;
if (typeof(effect) == 'string') {
E = this.effects.find(function(e) { return e && e.name === effect; });
}
else {
E = effect;
}
if (!V().inbattle || ((!E.ULTIMATESTICKY || mods.unsticky == "ultimate") && (!E.sticky || mods.unsticky))) {
E.onRemove(this);
this.effects.delete(E);
return E.removeText(this.name);
}
else {
return `${E.name} status can't be removed!<br/>`;
}
} else {
return `${this.name}'s Stasis held the effect in place!<br/>`;
}
}
}}}
The next set of functions governs how status effects interact with the character. They effectively bundle three functionalities together for us: adding or removing the Effect object in the character's {{{effects}}} array, applying the effect's actual functionality, and sending a message to the player.
Note that these functions have branching functionality depending on the arguments they receive:
{{{
if (typeof(effect) == 'string'){
effect = new Effect(effect,time,power);
}
}}}
{{{addEffect}}} is designed to take a single argument -- an Effect object -- by default. This is because it has to call {{{onApply}}} and {{{addText}}}, which are functions of Effect objects. The standard structure of effect management is set up to be compatible with format. However, what if you want to make a new effect and add it at the same time? This sometimes happens in special circumstances, such as Darwin's power up scene in <i>Cartoon Battle</i>. You could say {{{addEffect(new Effect(name,time,power))}}}, but this is a little awkward to write. It's easier if we're just able to pass the raw variables directly, as we would when constructing a new Effect. That's what this {{{if}}} statement does: If the first argument is a string, the function assumes the user is trying to construct a new Effect object, and does so before proceeding.
{{{
if (typeof(effect) == 'string') {
E = this.effects.find(function(e) { return e && e.name === effect; });
}
else {
E = effect;
}
}}}
We see something similar in {{{removeEffect}}}. By default, it is designed to take a single object -- an Effect object present in the Actor's list of {{{effects}}} -- so it can remove it through the {{{delete}}} function. This assumption is valid for the default engine structure, where effect removal actions iterate over the {{{effects}}} array and remove elements non-specifically. But what if we <i>do</i> want that specificity? What if we only want to remove a specific effect? We could iterate over the {{{effects}}} array and check against the name of the effect we want to remove every time, but that's tedious. Instead, we can just pass a string, and the work of searching is offloaded to this function. (Note, however, that due to the way {{{find}}} works, this will only remove the <b>first</b> effect it finds, and leave the others alone. If you want to remove all instances of a stackable effect, you will indeed need to loop through the whole array to collect them.)
If you call these functions with a {{{<<print>>}}} statement, the {{{return}}} statement will be printed to the screen. You don't need to call it again; the code of a function is executed regardless of the context in which it is called, so it'll still run in addition to printing the text. If you want to discard the output, all you need to do is call the function with a {{{<<run>>}}} statement instead; it will still perform its functionality, but you won't print the {{{return}}} statement.
{{{
if (!V().inbattle || ((!E.ULTIMATESTICKY || mods.unsticky == "ultimate") && (!E.sticky || mods.unsticky)))
}}}
Additionally, <code>removeEffect</code> has this clause before it removes an effect. "Sticky" effects aren't supposed to be removable by normal means. We could manually implement this functionality into every cure or dispel ability, but that's tedious. Since this is universal behavior, we'll implement it here. If you want a special ability to be able to remove sticky effects, such as Witch's "Renewal" ability, you can pass an {{{unsticky}}} flag here.
Certain effects have an "ULTIMATESTICKY" property as well that prevents them from being removed even by normal unsticky abilities. You can still remove these, but you will need to assign the string "ultimate" to the {{{unsticky}}} variable.
(We also have to be careful -- we still want to be able to remove sticky effects through system cleanup, such as end-of-battle processing! I account for this by implementing a bypass if the game isn't currently in a battle, but if you want sticky effects to persist after battle you will have to do something different.)
<h4 id="JS.actor.equipFunctions">Equipment Functions</h4>
{{{
unequip (slot,index,mods) {
mods = (mods || {});
if (this.equipment.has(slot)) {
var item = null;
if (this.equipment.get(slot) instanceof Array) {
if (typeof(index) == 'number') {
item = this.equipment.get(slot)[index];
} else { console.log("ERROR in unequip: Invalid item index."); return; }
} else {
item = this.equipment.get(slot);
}
if (item !== null){
if (item.default === undefined && mods.destroy !== true) {
inv().addItem(item.name);
}
if (item.onRemove !== undefined) {
item.onRemove(this);
}
(this.equipment.get(slot) instanceof Array) ? this.equipment.get(slot)[index] = null : this.equipment.set(slot,null);
//this.equipment.set(slot,new Item("Default "+slot));
}
}
else { console.log("Attempted to unequip nonexistent slot."); return; }
}
}}}
There are similar functions for handling equipment, though in this case we define the removal function first. {{{unequip}}} is straightforward in most cases: it is passed the slot to unequip, and the item in that slot is removed. We require a common-sense check to ensure the given slot exists and isn't already empty. We also want to store the unequipped item and place it back in the player's inventory, as the {{{set}}} function will simply overwrite what was originally in the slot. (If we <i>do</i> want the player to permanently lose the item, we can set the {{{destroy}}} flag to {{{true}}}.)
Things become slightly more complex if we are looking at an equipment slot with multiple subslots, such as "Accessory" in the default engine. If we're unequipping from such a slot, the function must be passed an {{{index}}} value so it knows which subslot to unequip. It will then check at the beginning if the slot is an array; if it is, it will assign the item at the index value to {{{item}}}, instead of the slot itself. The function checks once more at the end so that it can correctly target the slot to set to {{{null}}}.
There is an optional statement here for systems that cannot function with a {{{null}}} equipment slot; for instance, most versions of <i>Dungeons & Dragons</i> must read the statistics of the equipped weapon for attacks. In this case, instead of setting the slot to {{{null}}}, you would want to replace it with another object. These default objects can be defined in the item database like any other.
There is also an {{{unequipAll}}} function, which simply runs through the Actor's {{{equipment}}} and calls this on each item.
{{{
equip (item) {
if (this.equipment.has(item.equippable.slot)) {
var slot = this.equipment.get(item.equippable.slot);
var existing = null;
var subslot = undefined;
if (slot instanceof Array) {
for (let i = 0; i < slot.length; i++) {
if (slot[i] === null) {
subslot = i;
break;
}
}
if (subslot === undefined) {
subslot = slot.length-1;
existing = slot[subslot];
}
}
else {
existing = slot;
}
if (existing !== null){
this.unequip(item.equippable.slot,subslot);
}
(slot instanceof Array) ? slot[subslot] = item : this.equipment.set(item.equippable.slot,item);
if (item.onEquip !== undefined){
item.onEquip(this);
}
if (inv() !== undefined && inv().has(item.name)){
inv().decItem(item.name);
}
} else { console.log("ERROR: Equipment type not recognized"); return; }
}
}}}
The {{{equip}}} function is similar; however, to equip something we have to unequip what was already there! Fortunately, we've defined {{{unequip}}} already, so we can simply call it (after making common-sense checks that the slot exists and isn't empty). Then we assign the item to the slot and run any {{{onEquip}}} functionality, if it exists. We will also want to remove the item from the player's inventory; the {{{set}}} statement creates a new copy, so if we didn't remove the original we'd be duplicating the item!
Subslot compatibility is more complicated for this function. If the slot is an array, we need to search through it with a {{{for}}} loop to find the first available subslot (the first slot with contents {{{null}}}). As soon as it finds an empty slot, we {{{break}}} out of the loop to save processing power. This ensures that equipping additional items to this slot will fill the subslots in ascending order.
What if there are no empty subslots? In this case, {{{subslot}}} remains {{{undefined}}} and the {{{if}}} statement triggers, targeting the last subslot to swap with the new item.
<h4 id="JS.actor.toleranceFunctions">Tolerance Functions</h4>
{{{
decTol (k) {
this.tolerances.get(k).currentVal--;
}
resetTol (key) {
this.tolerances.get(key).refill();
}
}}}
We also have functions that simply shorten common tasks for handling tolerances. {{{decTol}}} decrements the current tolerance value (normally a mouthful to access, as you can see here), and {{{resetTol}}} resets the current tolerance value to its maximum.
(See <a class="noExternal" href="#effects1">Applying Effects</a> for more explanation on tolerances.)
<h4 id="JS.actor.misc">Miscellaneous Functions</h4>
{{{
regenHP () {
this.setHP(this.maxhp * this.HPregenPercent);
this.setHP(this.HPregenFlat);
}
}}}
{{{regenHP}}} shortens two lines of code into one. Because HP regeneration can have both flat and propotional attributes, we have to apply both when running HP regeneration.
{{{
get row () {
if (setup.BATTLE_GRID === true) {
var party;
if (this instanceof Enemy) {
party = V().enemies;
} else if (this instanceof Puppet) {
party = V().puppets;
}
for (var i = 1; i < setup.COLUMN_SIZE; i++) {
if (party.indexOf(this) < i * setup.ROW_SIZE) {
return i;
}
}
console.log("ERROR in row getter: could not find row");
return 0;
} else {
return;
}
}
get col () {
if (setup.BATTLE_GRID === true) {
var party;
if (this instanceof Enemy) {
party = V().enemies;
} else if (this instanceof Puppet) {
party = V().puppets;
}
for (var i = 1; i < setup.ROW_SIZE; i++) {
if (party.indexOf(this) % setup.COLUMN_SIZE == (i-1)) {
return i;
}
}
console.log("ERROR in column getter: could not find column");
return 0;
} else {
return;
}
}
}}}
These getters tell us the row and column addresses of a character if they are in a battle grid. See [[the Battle Grid|Additional Features]] for more information.
<h3 id="JS.stats">Stat Class</h3>
Stats also require their own class. If you intend for your stats to be totally static and never undergo temporary changes, defining them as a simple variable is fine; but what if you want to give players a ring that provides +1 Strength they can put on or take off at will, or a "weaken" status effect that decreases Strength for a given duration?
That is the main function of this class, defined in {{{stat class.js}}}.
{{{
constructor (base) {
this._base = base;
this._mods = {};
this._current = undefined;
}
}}}
The constructor isn't too special; we just initialize some properties.
{{{
addMod (id, mod, equipment) {
id = String(id);
var mods = this._mods[id] = this._mods[id] || [];
if(Number.isFinite(mod)) {
mod = { add: mod };
if (equipment === true) {
mod.equipment = true;
}
}
if(typeof mod === "object") {
var idx = 0;
mods.forEach(function(mod) { idx = Math.max(idx, mod.idx); });
mod.idx = (++ idx);
mods.push(mod);
this.clearCache();
return mod.idx;
}
}
}}}
The {{{addMod}}} function is the main purpose of this class. The way it works is this: Every {{{Stat}}} has a {{{_mods}}} property that initializes as an empty object, <code>{}</code>. This empty object acts as a container for every modifier that acts on the stat. Every time you want to add something that modifies the stat, such as a piece of equipment, you pass a name ({{{id}}}), and {{{_mods}}} will gain a property with that name that contains the modifier data.
However, there's an additional wrinkle: What if you want multiple mods of the same type to stack, such as applying multiple copies of the same status effect, or wearing two Rings of +1 Strength at the same time? And object can't have multiple properties with the same name; attempting the second assignment will simply overwrite the existing property. To account for this, each mod's name points to an <i>array</i> of modifier objects, rather than just one modifier object. We initialize this through the following statement:
{{{
var mods = this._mods[id] = this._mods[id] || [];
}}}
In English, this says, "If {{{this._mods}}} already contains a property with the given {{{id}}}, copy it; if it does not, create a property with the given {{{id}}} and make it an empty array. Then, define a new variable {{{mods}}} and assign it to {{{this._mods}}}." Essentially, this statement is evaluated from right to left instead of left to right.
We then check if the passed {{{mod}}} is a finite number. If it is, we convert it into object form, attaching the passed value to an {{{add}}} attribute. We also check if the {{{equipment}}} argument has a value of {{{true}}}; if so, we mark the mod as an equipment-based modifier by adding an {{{equipment}}} attribute.
(We can bypass this code if we pass an object to the {{{mod}}} argument; this is useful if you want to create other, special properties for your mod. However, this functionality works for most purposes.)
After this processing, the finalized mod is added to the {{{mods}}} array. For accurate recall, every mod in the array needs a unique identifier, which we add through the {{{idx}}} variable and the following function:
{{{
mods.forEach(function(mod) { idx = Math.max(idx, mod.idx); });
mod.idx = (++ idx);
}}}
In English, this says: "For every mod in the mods array, compare the existing {{{idx}}} value to the mod's {{{idx}}} value, and set {{{idx}}} to whichever value is higher. Then, increment {{{idx}}} by 1 and assign it to the mod object we want to add to {{{mods}}}." This allows us to ensure that every mod has a unique {{{idx}}} value, and that they increment in logical order.
Because adding a mod obviously causes a change in the stat's modifiers, we next run {{{clearCache()}}}, which recalculates the stat's current value.
We end by returning the value of {{{idx}}}. This is useful for storing the specific mod's index to another variable so we can recall it for accurate removal later.
As an example of how this is used, here is the data for a status effect that does exactly what it sounds like, ATK Boost:
{{{
'ATK Boost': {
"buff": true,
"stackable": true,
"statmod": true,
"onApply": function (puppet) {
this.id = puppet.stats.addMod("Attack","ATK Boost",this.power);
},
"onRemove": function (puppet) {
puppet.stats.removeMod("Attack","ATK Boost",this.id);
},
"info": function (effect) {
return `Attack boosted by ${this.power}.`;
},
"addText": function (target) {
return `${target} is surging with strength!`;
},
"removeText": setup.effectFunctions.remBuff
}
}}}
When an ATK Boost is applied to a character, they gain a mod with the name "ATK Boost" that points to the array {{{[ {add: <power>, idx: 1} ]}}}, and the ATK Boost object gets the ID of the mod, {{{1}}}, assigned to its {{{id}}} property. This is a stackable effect, so if the character got a second ATK Boost, it would be added to the "ATK Boost" array, which would now look like this: {{{[ {add: <power>, idx: 1}, {add: <power>, idx: 2} ]}}}
{{{
removeMod (id, index) {
id = String(id);
if (Number.isFinite(index)) {
this._mods[id].deleteWith(function(mod) { return (mod.idx === index); })
} else {
delete this._mods[id];
}
this.clearCache();
}
}}}
To remove a mod, we need only the mod's ID and, optionally, its index within the ID. If no index is passed, this function simply deletes all mods under the given ID, which is fine for non-stackable effects. If an index is passed, we instead delete only the mod with that index value, using SugarCube's {{{deleteWith}}} function.
{{{
get current () {
if (this._current === undefined) {
/* gather multipliers */
var mult = Object.values(this._mods)
.reduce(function(bigSum, entry) { return bigSum + entry
.map(function(m) { return Number.isFinite(m.mult) ? m.mult : 0; })
.reduce(function(sum, add) { return sum + add; }, 0);
}, 0);
/* gather sums */
var add = Object.values(this._mods)
.reduce(function(bigSum, entry) { return bigSum + entry
.map(function(m) { return Number.isFinite(m.add) ? m.add : 0; })
.reduce(function(sum, add) { return sum + add; }, 0);
}, 0);
this._current = this.base * (mult + 1) + add;
}
return this._current;
}
}}}
Here is where it all comes together. When you call for a stat's {{{current}}} property, you don't get {{{_current}}}; the stat is calculated based on the base value and all modifiers.
This definition has functionality for both multiplier and incrementor modifiers, and has a sub-section for each. They're both fundamentally similar, however.
Note that the following happens all at once. Don't let the line breaks fool you; if there's no semicolon, the code doesn't stop.
{{{
Object.values(this._mods)
}}}
{{{Object.values()}}} extracts the values of an object's properties and converts them into an array. This is useful because arrays have additional functions that make finding and extracting data easier. We use this to get a list of all the mods in {{{_mods}}} in array form, then:
{{{
.reduce(function(bigSum, entry) { return bigSum + entry
}}}
The {{{Array.reduce}}} function defines an accumulator value, and then uses it in a function it applies to every element in the array. This is most commonly used for quickly summing all the values in an array of numbers, and that's exactly what we want to do here, after a fashion. However, remember that every mod in {{{_mods}}} is an array of objects, not values, so we need to go deeper. We need to perform another function on each {{{entry}}}:
{{{
.map(function(m) { return Number.isFinite(m.add) ? m.add : 0; })
}}}
The {{{Array.map}}} function returns an array that is the result of performing a function on every element in the array. In this case, we use it to extract a specific property from our modifier objects: if the objects have a finite {{{add}}} value (or {{{mult}}} value, if we're tracking multipliers), it's returned and placed into a new array.
So we now have an array of the numeric data of our modifiers. We can compress that into a single value with another...
{{{
.reduce(function(sum, add) { return sum + add; }, 0);
}}}
This one is very straightforward: we simply run through every value in the array and add them together.
Going back to our initial {{{reduce}}}, this sum is the value that will finally be returned after all the processing we've done on {{{entry}}}. It's then added to {{{bigSum}}}, and the process starts again for the next type of modifier. Once this is done, we'll have all our modifiers compiled into one final value.
{{{
this._current = this.base * (mult + 1) + add;
}}}
Then, at last, we can calculate what the current stat value should be. It's this value that's returned.
Notice this {{{if}}} statement at the start:
{{{
if (this._current === undefined)
}}}
All of this calculation only occurs if {{{_current}}} is {{{undefined}}}. This is very efficient, because it means the stat is only calculated when it needs to be. We only have to set {{{_current}}} to {{{undefined}}} when it needs to be recalculated, and can otherwise continue using the same value.
{{{
Map.prototype.addMod = function (key, id, mod, equipment, type) {
if (type !== undefined && typeof(type) == 'string') {
return this.get(key)[type].addMod(id,mod,equipment);
} else {
return this.get(key).addMod(id,mod,equipment);
}
}
Map.prototype.removeMod = function (key, id, index, type) {
if (type !== undefined && typeof(type) == 'string') {
this.get(key)[type].removeMod(id, index);
} else {
this.get(key).removeMod(id, index);
}
}
}}}
Finally, I created these two functions to make some things a little easier. Many {{{Stat}}}s, such as elemental affinities and tolerances, are stored in {{{Map}}} objects, and would therefore necessitate a lengthy {{{get}}} call to access for mod addition or removal. With these, you only have to pass the name of the entry you want to target, and the function will find it for you. (A wrinkle exists for stats with multiple properties, such as elemental affinities having both soak and resistance, so you can also pass the {{{type}}} you want to target, if necessary.)
{{{
setup.statInfo = {
"Attack": "Increases damage of regular attacks.",
"Defense": "Reduces damage taken.",
"Special": "Improves effectiveness of status effects, mitigates received status ailments, and increases damage of item attacks."
}
}}}
Information on what each stat does is also defined in this file. This information is called as a tooltip in <a class="noExternal" href="#menus.status">the party menu</a>. I recommend defining these for any stats whose purpose is not immediately obvious, since the player needs to understand their basic toolset.
<h4 id="JS.stats.fill">The FillStat Class</h4>
Some stats can be "filled" and "depleted", with both a current and maximum value. This functionality is handled by the FillStat class, which is a subclass of the Stat class.
{{{
constructor (base) {
super(base);
this.currentVal = base;
}
}}}
The constructor is much the same, but it adds a new property, {{{currentVal}}}. Don't confuse this with the {{{current}}} value! Due to inheritance, we have to keep the name of the calculated stat value {{{current}}}, but this refers to the <i>maximum</i> stat value, not the current "points" in the pool.
{{{
clearCache () {
this._current = undefined;
this.refill();
}
refill () {
this.currentVal = this.current;
}
}}}
The mechanics of {{{currentVal}}} necessitate a slight change to the way {{{_current}}} is updated. When {{{_current}}} (the maximum stat value) is changed, we need to update {{{currentVal}}} to match it. This is accomplished through the {{{refill}}} function, and by running it in {{{clearCache}}}. <b>Note that this assumes you will not be modifying max values in the middle of battle, or that if you are, you want them to be refreshed alongside the change. If you want something different, you'll need to code different functionality.</b>
<h4 id="JS.tolerances">Tolerance Class</h4>
Tolerances (status ailment resistances) have additional properties that necessitate their own specialized form of {{{FillStat}}}.
{{{
get current () {
if (this._current === undefined) {
// check for immunity
let immunity = false;
if (this.base >= 0) {
Object.values(this._mods) // returns array of arrays
.forEach(function(modArray) {
modArray.forEach(function(mod) { if (mod.immune === true) {immunity = true;} });
});
} else {
immunity = true;
}
if (immunity === true) {
this._current = -1;
} else {
// gather tolerance
var tol = Object.values(this._mods)
.reduce(function(bigSum, entry) { return bigSum + entry
.map(function(m) { return Number.isFinite(m.add) ? m.add : 0; })
.reduce(function(sum, add) { return sum + add; }, 0);
}, 0);
this._current = this.base + tol;
}
}
return this._current;
}
}}}
The only change comes in the getter for {{{current}}}. In addition to partial resistance, it is possible for a tolerance to grant complete immunity to an ailment, and this has to be handled differently than in the normal {{{current}}} calculation.
{{{
if (this.base >= 0)
}}}
Obviously, if mods provide an immunity, calculating the resistance value is moot. This means we should check for immunity first. Immunity is conveyed through a negative tolerance value, so we can check if the base tolerance already provides it with this {{{if}}} statement. If the {{{base}}} is less than 0, we know the character is already immune, so we can skip the rest of this function; but if it's not, we need to check for immunity from mods.
{{{
Object.values(this._mods)
.forEach(function(modArray) {
modArray.forEach(function(mod) { if (mod.immune === true) {immunity = true;} });
});
}}}
The immunity check works differently from the other calculations. Instead of a total value, we only care about a binary state: is there an immunity or not? We therefore only need a regular {{{forEach}}} function to run through and test all the mods. If we find a mod with an {{{immune}}} property of {{{true}}}, we set the previously-defined {{{immunity}}} variable to {{{true}}} to record that we found an immunity.
If we found an immunity, we can set {{{_current}}} to -1 (a negative value, so it will be read as an immunity) and end the calculation. If we didn't, we gather tolerance values using the normal {{{Stat}}} code.
<h3 id="JS.puppets">Puppet Class</h3>
This file defines the statistics of the player characters. They're called "puppets" as a holdover from <i>Cartoon Battle</i>, but you can change the name if you like.
{{{
class Puppet extends Actor
}}}
To begin with, we say that the {{{Puppet}}} class <i>extends</i> {{{Actor}}}. This makes {{{Puppet}}} a <i>subclass</i> of the {{{Actor}}} class. It will inherit all of {{{Actor}}}'s object methods, and will count as an instance of {{{Actor}}} for data check purposes. We can still use {{{.get}}} with it, for instance. This is handy because it allows us to define unique attributes of puppets we don't want for enemies, while still maintaining access to all the useful attributes and methods defined in {{{Actor}}}.
Subclasses are constructed a little differently: they still have a {{{constructor}}} function, but it must use the parent class's constructor before it can make its own unique modifications. This is done through the keyword {{{super}}}. This can be thought of as us constructing an instance of the parent class and then modifying it with unique subclass features.
{{{
this.lastAction = null;
this.maxen = 10;
this.en = 5;
this.inspired = false;
this.defeats = 0;
this.kills = 0;
}}}
By default, that's these features: Energy values, the <code>inspired</code> flag, <code>kills</code> and <code>defeats</code> counters, and the <code>lastAction</code> property, which is initialized to a blank <code>null</code>. You can change this section if, for instance, you want unique Energy stats for every character.
{{{
for (let n of this.data.actions) {
this.actions.push(new Action(n));
}
this.defaultAction = new Action(this.data.defaultAction);
if (this.data.specialInit) { this.data.specialInit(this); }
}}}
For specific data unique to the character, we make calls to the database. Recall that most statistics, such as HP and core stats, are already handled by the <code>Actor</code> constructor. By defult, the subclass' constructor adds the character's actions and default action, with any miscellaneous modifications run through the <code>specialInit</code> function (assuming it exists).
You can also define unique method functions here if you need them. By default, there's a getter and setter for the {{{en}}} property that, like the HP setter, is designed to keep it within bounds, and some functions for calculating XP requirements.
<h3 id="JS.enemies">Enemy Class</h3>
Enemies are constructed similarly to puppets, with of course the major change that their {{{actions}}} property functions completely differently, as explained in [[Documentation (Basic)]]. However, their constructor has a few unique statements:
{{{
if (this.data.cooldown) { this.cd = new Map(Object.entries(this.data.cooldown)); }
}}}
You may recall that some enemies had a {{{cooldown}}} attribute in their entry. This statement extracts those values and assigns them to a Map, just as with elements and tolerances. This {{{cd}}} Map (short for "cooldown") can then be checked with the {{{CDcheck}}} function.
{{{
this._noAttacks = Number.isInteger(this.data.noAttacks) && this.data.noAttacks > 0
? new FillStat(this.data.noAttacks)
: new FillStat(1);
}}}
Enemies also have a special handler for their {{{noAttacks}}} attribute. If the {{{noAttacks}}} property of their database entry is a positive integer, the constructor creates a {{{FillStat}}} equal to that number; otherwise, their {{{noAttacks}}} defaults to 1.
{{{
get noAttacks () {
return this._noAttacks.currentVal;
}
set noAttacks (amt) {
this._noAttacks.currentVal = amt;
}
}}}
This property assignment requires an underscore because the getters and setters for the enemy's {{{noAttacks}}} property point to the {{{FillStat}}}'s {{{currentVal}}} property, rather than the {{{FillStat}}} itself.
{{{
if (setup.THREAT_TARGETING === true) {
this.threat = new Map();
puppets().forEach(function(puppet) {
this.threat.set(puppet.name,puppet.initialThreat());
}, this);
}
}}}
If your game uses threat targeting, enemies also generate a threat table with a value keyed to every puppet.
{{{
changeInto (name) {
if (setup.enemyData[name].hp) {
this.setMaxHP(setup.enemyData[name].hp);
}
if (setup.enemyData[name].stats) {
for (let [pn,v] of Object.entries(setup.enemyData[name].stats)) {
this.setBase(pn,v);
}
}
if (setup.enemyData[name].actions) {
this._actions = setup.enemyData[name].actions;
}
if (setup.enemyData[name].special) {
setup.enemyData[name].special(this);
}
}
}}}
Finally, there is the {{{changeInto}}} function. This is a quick way of modifying enemies, such as if they transform into another form with different stats or actions. You could define these alternate forms as entirely separate enemies and generate the new form with a constructor call, but that would overwrite all the enemy's current attributes, such as their active effects. {{{changeInto}}} allows the enemy to retain all attributes you do not change. The entries specified by the {{{name}}} argument are also entries in the database, as you can see in the entry for Big Dipper:
{{{
"Big Dipper": {
"special": function (actor) {
actor.idname = "Dipper";
actor.name = "Big Dipper";
},
"actions": function () {
(...)
}
}
}}}
Note the {{{idname}}} property here. You may recall from <a class="noExternal" href="#JS.actor">the Actor class</a> that this is used when fetching data from the database. The issue here is that but Big Dipper isn't a complete entry, as it's only intended to modify the base Dipper and I otherwise want to keep using the data from that entry -- but I do want the new name "Big Dipper" to display in battle, even though that will make the object point to this entry in the database. The solution is to make a new variable whose <i>only</i> function is to serve as a pointer for the database entry. Now, the enemy the player sees can have the official name of "Big Dipper", while still pointing to "Dipper" if it needs something that's not listed in "Big Dipper".
We also define several other method functions for enemies.
* {{{decCD}}} is a quick way of decrementing cooldowns at the start of every turn.
* {{{CDcheck}}} is a quick way of determining if a cooldown action is available. It returns {{{true}}} if the cooldown is less than 0 and {{{false}}} otherwise.
* {{{surrenderCheck}}} checks if an action violates an enemy's surrender by checking against the action's {{{truce}}} property. Note that, by default, this includes actions that don't affect the enemy at all; they are smart enough to understand that the only reason you'd be casting a buff is if you plan to attack them. You can change this logic if you want.
{{{
get priority () {
if (this._priority === undefined) {
return V().enemies.indexOf(this);
} else {
return this._priority;
}
}
}}}
As a final note, here is the getter for the {{{priority}}} attribute. This determines what order enemies act in during the enemy turn. To set a unique priority value, you will need to assign a value to {{{_priority}}}; otherwise, the enemies default to acting in index order.
<h3 id="JS.actions">Action Class</h3>
This class defines actions that characters use in battle. It should be quite straightforward, as unlike battling characters, action properties are almost always invariant and require little processing in the constructor or object itself. For more on how action properties are used, see <a class="noExternal" href="#actionphase">the Action Phase</a>.
There is also a subclass, {{{ItemAction}}}, that is used for consumable items. It functions in much the same way, but has a few different default values.
You may also want to look at the file {{{2_action functions.js}}}. This file contains several functions used to define common action code, such as adding an effect or dealing damage. This makes it easier to define actions. You can see it used in this example in the "act" property of the "Sword" entry: it was assigned the {{{justdmg}}} function, which, as the name implies, just does damage and nothing else. Corresponding preview code is stored under the {{{Prev}}} object as well. These are designed to be modular, and can be passed an {{{extension}}} argument to add additional functionality.
<h3 id="JS.items">Inventory and Items</h3>
{{{
window.inv = function inv () {return V().inv;}
}}}
When working with items, the very first thing we want to do is define our inventory. We need an inventory to put items in, right? A lot of code references the inventory, so we need a standardized way to refer to it. That's this: the {{{inv()}}} function returns whatever your actual inventory variable is. By default, it's a story variable, but you could rename it or tie it to specific characters.
{{{
class Inventory extends Map {
constructor(ItemArray){
var m = [];
ItemArray.forEach(function(item){
m.push([item.name,item]);
});
super(m);
}
addItem (name,amt) {
if (amt === undefined){
amt = 1;
}
if (this.has(name)){
if (this.get(name).stock + amt > setup.ITEM_MAX) {
let s = 1;
while (((this.get(name).stock + 1) < setup.ITEM_MAX) && s < amt) {
this.get(name).stock += 1;
s++;
}
return false;
} else {
this.get(name).stock += amt;
return true;
}
} else {
this.set(name,new Item(name,amt));
return true;
}
}
decItem (name,amt) {
if (this.has(name)){
if (amt === undefined){
amt = 1;
}
var v = this.get(name);
v.stock -= amt;
if (v.stock <= 0){
this.delete(name);
}
return;
} else {
return "ERROR in decItem: item name not found in inventory\n";
}
}
}
}}}
Then, we should probably define what functionality we want our "inventory" to have. The {{{Inventory}}} class extends the {{{Map}}} object, meaning it functions the same way: if you need to reference an item, you can just call up its name. However, we've also added some method functions, and modified the constructor.
{{{
constructor(ItemArray){
var m = [];
ItemArray.forEach(function(item){
m.push([item.name,item]);
});
super(m);
}
}}}
Instead of how you would normally construct a {{{Map}}} (an array of key/value pairs), you can construct an {{{Inventory}}} with just an array of items. The constructor will automatically extract the {{{name}}} attribute from the item and turn it into the key for that entry.
{{{
addItem (name,amt) {
if (amt === undefined){
amt = 1;
}
if (this.has(name)){
if (this.get(name).stock + amt > setup.ITEM_MAX) {
let s = 1;
while (((this.get(name).stock + 1) < setup.ITEM_MAX) && s < amt) {
this.get(name).stock += 1;
s++;
}
return false;
} else {
this.get(name).stock += amt;
return true;
}
} else {
this.set(name,new Item(name,amt));
return true;
}
}
}}}
We also require special functionality for adding an item to the inventory. We run into the issue that if an item already exists in the {{{Inventory}}}, we only want to add to its {{{stock}}} value rather than creating a whole separate key. That's what this function checks for: if the {{{Inventory}}} already has the given item, it just increases the {{{stock}}} of that item, and if it doesn't, it makes a new entry with a {{{set}}} command.
We also include a check against {{{ITEM_MAX}}}. If adding the item to the inventory would exceed the item cap, we'll run a loop up to the value of {{{amt}}} and increment the item's stock by 1 each time until we reach the cap. This will allow the player to take as much as they can carry if they get a huge windfall of items, instead of getting nothing. The function will also return {{{false}}}, allowing your code to know that the player couldn't store all of the treasure. <b>Note that there's no corresponding code to automatically decrement the "stock" of a partially obtained treasure. For now, you will have to keep track of that manually.</b>
(This assumes you want your inventory to function in this way, with items being "stackable". Some games don't allow this, and make every item appear as its own instance in the inventory. If you want this kind of behavior, you could base {{{Inventory}}} on an {{{Array}}} instead, but be warned that it will be very hard to refer to an item after it's been added.)
There is a corresponding function, {{{decItem}}}, that operates similarly: it decreases {{{stock}}}, and deletes the item from the {{{Inventory}}} if there's none left. You can change this if you do want items to still appear even at 0 stock.
(If you ever want to completely clear an item, you can use {{{Map}}}'s {{{.delete}}} function directly.)
Items themselves are constructed exactly like <a class="noExternal" href="#JS.actions>actions</a>: the class definition itself only defines getter functions to fetch data from a database, and the actual details of the items are defined in the {{{itemData}}} object.
<h3 id="JS.support">Support Functions</h3>
There are several other useful functions storied in {{{1_support-functions.js}}} designed to make some common tasks easier.
{{{
window.target = function target () {return State.variables.B === undefined ? null : State.variables.B.target;};
window.subject = function subject () {return State.variables.B === undefined ? null : State.variables.B.subject;};
window.action = function action () {return State.variables.action;};
}}}
A big draw are these functions, which allow us to more conveniently access the active target, subject, and action in battle. You'll be seeing these a lot. (However, be aware that <b>you cannot use a function to call a variable for assignment.</b> For example, you cannot say {{{target() = Object}}}. You must say {{{V().B.target = Object}}}.)
{{{
Map.prototype.inc = function (key,amt) {
this.set(key,this.get(key)+amt);
return;
}
}}}
This is a simple function added to the {{{Map}}} prototype, which means that all {{{Map}}}s we use will have access to it. This is a relative setter that <i>adds</i> the passed value to to the value in the {{{Map}}}, as opposed to an absolute setter like the default {{{Map.set}}} function.
{{{
window.deadCount = function deadCount () {
let count = 0;
puppets().forEach(function(puppet) {
if (puppet.dead) {
count++;
}
});
return count;
}
}}}
We have a function for quickly representing the number of defeated characters. This is useful when calculating <a class="noExternal" href="#victorycheck">victory and defeat conditions</a>.
{{{
window.puppets = function puppets () {
return V().puppets.filter(function(p) { return p !== null });
}
window.enemies = function enemies () {
return V().enemies.filter(function(p) { return p !== null });
}
}}}
These functions return party arrays with all {{{null}}} entries removed. This is helpful if you want to use the battle grid.
{{{
setup.textWidth = function(text, bold, size) {
/* create the <span> to measure the text width */
if (bold === undefined) {
bold = "normal";
}
else if (bold === true) {
bold = "bold";
}
if (size === undefined || typeof(size) != 'number') {
size = "12pt";
} else {
size = Number.toString(size) + "px";
}
var tElement = jQuery(`<span style="font-weight: ${bold}">` + String(text) + "</span>");
/* add it (hidden) to the end of the document's body so that
the browser updates its width and we can save it */
tElement.hide().appendTo(document.body);
var width = tElement.width();
/* clean up */
tElement.remove();
return width;
};
setup.scaledTextDiv = function(text, width, bold, size, print) {
var tWidth = setup.textWidth(text, bold, size);
if (print !== undefined) {
text = print;
}
if (temporary().enemy !== undefined && temporary().enemy.large) {
return `<div>${text}</div>`;
} else if(tWidth < width) {
return `<div style="width: ${width}px; overflow: hidden;"><span style="display: inline-block; white-space: nowrap;">${text}</span></div>`;
} else {
return `<div style="width: ${width}px; overflow: hidden;"><span style="display: inline-block; white-space: nowrap; transform: translate(-50%, 0) scaleX(${width / tWidth}) translate(50%, 0);">${text}</span></div>`;
}
};
}}}
These functions allow you to scale text to fit it within a certain width; the text will be compressed instead of overflowing or breaking its container. This is useful if you have variable elements displaying inside a container that must have a fixed width, such as the actor boxes. Though the code here is complicated, the function is simple to use: Simply pass the text you want to scale, the maximum width you wish to allow, whether or not the text is bold, its font size (in pt), and what you want the text to finally display if that's different than the original. (This is useful for, say, making links or other macros containing text we want scaled -- you can pass the text as the {{{text}}} argument and the macro as the {{{print}}} argument, and it will work.)
{{{
(function(window){
// A full compatability script from MDN:
var supportPageOffset = window.pageXOffset !== undefined;
var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat");
// Set up some variables
var statusbar;
var noHorizontal;
var noVertical;
// Add an event to the window.onscroll event
window.addEventListener("scroll", function(e) {
statusbar = document.getElementById("status");
noHorizontal = document.getElementById("noHorizontal");
noVertical = document.getElementById("noVertical");
// A full compatability script from MDN for gathering the x and y values of scroll:
var x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft;
var y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
if (noHorizontal) {
noHorizontal.style.left = -x + 1250 + "px";
}
if (statusbar) {
statusbar.style.left = -x + 1020 + "px";
}
if (noVertical) {
noVertical.style.top = -y + 50 + "px";
}
});
})(window);
}}}
This function is designed to allow the status pane to scroll with the user's viewport. Currently, the offset numbers used in this function do not automatically scale to the width of the status or battle panes, and must be tweaked manually. Don't change them or the attributes of the {{{#status}}} element unless you know what you're doing.
{{{getActor}}} is discussed with <a class="noExternal" href="#chain">the {{{<<chain>>}}} widget</a>.
{{{reverseChildren}}} and {{{guardCheck}}} are discussed with [[the Battle Grid|Additional Features]].
<h3 id="JS.serial">Serialization and Save Files</h3>
If you look at the end JS files, you may notice that every class has a variation on these two functions at the end:
{{{
<Object>.prototype.clone = function () {
// Return a new instance containing our current data.
return new <Object>(this);
};
<Object>.prototype.toJSON = function () {
// Return a code string that will create a new instance
// containing our current data.
const data = {};
Object.keys(this).forEach(pn => data[pn] = clone(this[pn]));
return JSON.reviveWrapper('new <Object>($ReviveData$)', data);
};
}}}
You may also notice that several classes have an odd addition to their constructors:
{{{
if (typeof(<1st argument>) == 'object'){
Object.keys(<1st argument>).forEach(prop => this[prop] = clone(<1st argument>[prop]));
}
}}}
These features are necessary to make custom classes work with Twine's save/load feature. Essentially, every time a save is loaded, the game must generate a completely new instance of every object via its constructor. The {{{toJSON()}}} function, which in turn depends on the {{{clone()}}} function, tells the program how to do this.
It would be a huge hassle to need to pass every single one of an object's properties to its constructor, so we can simplify things by instead passing it an object and assigning its properties to the new object. If this passed object is a clone of the old one, the new object will be reconstructed perfectly. However, we still want a "normal" constructor for constructing completely new objects. To accomplish both functionalities, we add {{{if (typeof(<1st argument>) == 'object')}}} to the start of the constructor, to branch its functionality. This tells the program that if we pass an object to the constructor instead of a normal variable, it should copy over that object's properties instead of going through the normal construction.
This is accomplished through {{{Object.keys(<1st argument>).forEach(prop => this[prop] = clone(<1st argument>[prop]))}}}. In English, this says, "Assemble a list of every property name in the object passed as the 1st argument; then, for every one of those properties, clone the property in the passed argument and assign it to a property of the same name in this new object we're creating." Or, alternatively, "Make this object a clone of the 1st argument." The only trick is that we need to do the exact same thing in the {{{toJSON()}}} function to create an object to pass to the revival method, because just passing the existing object ({{{this}}}) to {{{reviveWrapper()}}} will create an error.
(If our custom object doesn't reference any custom objects itself, we can use the shorter {{{Object.assign(this,<1st argument>)}}}. However, this method ensures all properties are cloned correctly too, so it is best to use it just to be safe.)
<b>Warning:</b> Arrays return 'object' when passed to {{{typeof()}}}. If you want your normal constructor to read an array, you will need to use {{{instanceof Array}}} instead.
<h3 id="JS.updates">What if we want to update something in a new version?</h3>
Let's say one of your beta testers discovers an action or item is incredibly gamebreaking. Not a problem, you say, you'll just tweak some values in the database and all new games will use those values instead. But wait! People who are already playing the game and have the old version saved will still have the old values! How can you ensure they get updated too?
The structure of the database actually means this won't be a problem. We tie action, effect, and item data to the {{{setup}}} variable, which exists independently of save game states. It is specifically designed for exactly this scenario -- any changes to {{{setup}}} will be reflected in existing saves, no additional modification necessary. You should use {{{setup}}} for any variables you intend to remain constant throughout a playthrough.
But what if you need to change something that isn't part of {{{setup}}}? Let's say you realize you made a typo in a variable adjustment when writing the consequences for a certain choice the player can make during the course of the game. Well, don't worry, you can fix that too.
{{{
Config.saves.onLoad = function (save) {
switch (save.version){
// You would make a case for older version numbers, and adjust variables as necessary. If you've made variable changes since that would cause conflicts with the save, you can update them here.
case 0:
/*
save.version++;
save.state.history.forEach(function (moment) {
//Access story variables through moment.variables
});
*/
default:
// all is (hopefully) well, do nothing
}
};
}}}
In the {{{0_config.js}}} file, you will see this. {{{onLoad}}} is a function built into SugarCube that is called before loading any save, and is particularly useful for updating saves to current versions. The save game version (which you can set with {{{Config.saves.version}}}) is placed into a {{{switch}}} statement here, allowing you to run unique code depending on the version. You'll want to increment the save version in each case so you don't keep running the same code every time you load a save in your current version. (Note that this is distinct from the official version number you would display in the game itself; you only need to update the save version when you need to make another case for {{{onLoad}}}.)
There is one trick involved here. See, you can't access the variables in a save through the normal {{{variables()}}} and {{{temporary()}}} functions, because those only refer to the <i>existing</i> story state. To modify a save, you have to call the variables in each {{{moment}}} of its {{{history}}} attribute. This code has already written out how to do that for you: Run a {{{forEach}}} loop over the save's {{{history}}}, and then modify the {{{variables}}} of each {{{moment}}} in the {{{history}}}. It may sound complicated, but other than that wrapper, you can access and modify variables the same way you would otherwise.
I recommend copying this code to a new case every time you make an update function so you don't forget it.
<h2 id="display">Display and aesthetics</h2>
[img[setup.ImagePath + "documentation/002.PNG"]]
Visual display -- the color and placement of the text, the borders around blocks, and the positioning of elements -- is determined by the <b>story stylesheet</b>, which can be accessed by clicking on the story title in the editor. This stylesheet uses a different coding language than Twine's script: CSS, or <b>Cascading Style Sheets</b>.
By default, the content width is optimized for three-character parties, so you may want to extend it if you are looking for larger party sizes. With more advanced CSS elements, such as a grid, you could even incorporate features like a battle map where characters can move in two-dimensional space.
If you didn't understand any of that, don't worry! You don't need to know CSS to use the engine, just if you want to make it look different.
<h3 id="statusdisplay">Displaying the status pane</h3>
{{{
<span id="status">
<<include status>>
</span>
<span id="content">
<span id="actorlist">
<<include "actorlist">>
</span>
<div id="phase">
</div>
</span>
}}}
This is the body code for the "Battle!" passage. We'll get more into the rest later, but for now, just notice that the status pane is in a separate {{{<span>}}} from everything else. This is necessary to get it to display in its proper spot, directly to the right of the battle content. The stylesheet code responsible for the correct display can be seen here:
{{{
.passage.battle {
width: 1030px;
}
#content {
float: left;
width: 640px;
border: dotted 1px;
padding: 1em;
}
#status {
float: right;
width: 300px;
min-height: 300px;
padding: 1em;
border-left: 1px solid;
font-weight: bold;
}
}}}
The key component here is that <b>the passage itself</b> is set to be wider than the {{{#content}}} and {{{#status}}} widths together. If you don't do this, the two sections will crash into each other and display on separate lines, like so:
[img[setup.ImagePath + "documentation/003.PNG"]]
Note, however, that this width is only fixed in passages tagged "battle". In all other passages, the passage width will scale to fit the resolution of the screen. This makes things easier for smaller monitors, but you'll need to make sure to tag any passage where you want to the status pane to display.
You can tweak the widths in the stylesheet depending on your preferences.
<h3 id="nobr">The nobr tag</h3>
[img[setup.ImagePath + "documentation/004.PNG"]]
You may notice a gray {{{nobr}}} tag at the top of several passages. <b>These are really important for preventing unwanted whitespace.</b> Twine parses all line breaks by default, even when you only use them to organize code. To make your code readable without creating tons of empty lines in the process, you'll need to use {{{nobr}}}. The downside to this is that when you <i>do</i> want a line break, you will need to add it manually with the {{{<br/>}}} tag.
<h3 id="pronouns">Personalized text</h3>
[img[setup.ImagePath + "documentation/005.PNG"]]
So English has these annoying things called "gendered pronouns". As you can see in the above example from an earlier version of the engine, they make natural-sounding system text a little tricky.
I tried to sidestep this issue by avoiding pronouns entirely in system messages, but eventually gave up and defaulted to the gender-neutral "they". This is great for <i>Cartoon Battle</i>'s genderless puppets, but sounds awkward when applied to other characters; not to mention you may want to have characters go up against animals or monsters, for whom "it" pronouns may be more appropriate.
{{{
getPronouns (type) {
if (typeof(type) == 'string') {
var pr;
switch(this.gender.toLowerCase()) {
case 'f':
case 'female':
pr = {obj: "her", subj: "she", pos: "her"};
break;
case 'm':
case 'male':
pr = {obj: "him", subj: "he", pos: "his"};
break;
case 'n':
case 'neuter':
case 'neutral':
case 'agender':
pr = {obj: "them", subj: "they", pos: "their"};
break;
default:
pr = {obj: "it", subj: "it", pos: "its"};
}
var result = pr[type];
if (result === undefined) { console.log("ERROR in getPronouns: invalid pronoun type"); }
return result;
}
else {
console.log("ERROR in getPronouns: no pronoun type passed");
return undefined;
}
}
get them () {
return this.getPronouns("obj");
}
get they () {
return this.getPronouns("subj");
}
get their () {
return this.getPronouns("pos");
}
get theyare () {
switch (this.they) {
case "they":
return "they are";
default:
return (this.they + " is");
}
}
}}}
To solve this, I made gender an accessible attribute of characters, and getter functions for determining each type of pronoun.
Twine will automatically print naked variables, so to use them you can just place them in text like this: {{{_subject.name swings _subject.their sword!}}} That will evaluate to "Ros swings their sword!" if {{{_subject}}}'s pronouns are set to neutral, or "Monster swings its sword!" if {{{_subject}}}'s pronouns are default, and so on.
<h3 id="healthbars">Health Bar Formatting</h3>
Battling characters have health bars, provided by <a href="https://twinelab.net/custom-macros-for-sugarcube-2/#/meter-macros" target="_blank">Chapel's meter macro set.</a> You can define the color of the enemy and player bars in StoryInit; by default, they are set to red and green, respectively. If you want to edit the meter's appearance further, you can find the macro in {{{bundle.js}}}, the source code for all of Chapel's custom macros. Find this section:
{{{
var $wrapper = $(document.createElement('div'))
.addClass('chapel-meter')
.attr({
'data-val' : value,
'data-label' : this.settings.label
})
.css({
'position' : 'relative',
'background-color' : this.settings.back,
'height' : this.settings.height,
'width' : this.settings.width,
'overflow' : 'hidden',
'border-radius' : '3px'
});
}}}
You can edit the {{{.css}}} attribute here to add any CSS formatting you wish to the meter. Just understand that the attribute names and values must both be strings (so, in quotes). Here, you can see that I have added a {{{border-radius}}} attribute to the default formatting, which is what gives the meters their rounded look. If you prefer them more angular, you can remove that attribute.
<h3 id="battlestyle">Special Aesthetics for Battles</h3>
There may be instances where you want certain battles to look different. Maybe the player is fighting a scary enemy in a dark room, and you find that the engine's default bright white background undercuts the atmosphere.
There is functionality for this in the "Preparation" passage, called before all battles:
{{{
<<callEncounter $scenario>>
<<if typeof($B.style) == 'string'>>
<<addclass "html" $B.style>>
<</if>>
}}}
If you set the {{{style}}} property in the encounter call or anywhere else, the code will add that class to the story document. You can add whatever formatting you want to this class or others in the stylesheet files. In the default engine, the "dark" class has the following format:
{{{
html.dark body
{
background-color:#111;
color:#fff;
}
}}}
This is the default SugarCube style: white text on a dark gray background.
<b>NOTE:</b> Modifying the document in this way affects the entire story, not just the current passage. This is <b>good</b> for our purposes, because the battle engine actually consists of multiple passages. This way, we don't need to worry about the custom formatting disappearing on passage jumps. <b>However,</b> you will need to undo this change if you don't want the story to keep looking like the custom format for the rest of the game. This is handled through the {{{<<endofbattle>>}}} widget:
{{{
<<if $B.style>>
<<addclass "body" $B.style>>
<<removeclass "html" $B.style>>
<</if>>
}}}
(Why add the class to the {{{body}}} element first? Because otherwise, there will be a "flicker" of default formatting in the victory passage before the player is forwarded to the next passage. Unlike {{{html}}}, the {{{body}}} element is refreshed between passages, so this still removes the styling while ensuring a smoother transition.)
<h3 id="savesmodifier">Modifying the Save Display</h3>
[img[setup.ImagePath + "documentation/default_save.PNG"]]
By default, SugarCube uses a passage excerpt to describe a save in the save menu. This is useful for most text adventure games, where there is little else to worry about other than the player's position in the story.
<<nobr>>
<center>
<figure>
<img @src="setup.ImagePath+'documentation/sojourner_save.PNG'" />
<figcaption>
<span style="font-size:10pt">(Screenshot from <i>Sojourner</i>, taken by myself)</span>
</figcaption>
</figure>
</center><</nobr>>In an RPG, there are potentially many more variables that are relevant to the player, beyond just the current location or chapter in the story. This screenshot (from an RPG Maker VX Ace game) is an extreme example, with a very large save screen that contains a lot of information. Some of this information you may consider extraneous, but the party level, composition, and finances are likely relevant when remembering which save to choose.
[img[setup.ImagePath + "documentation/custom_save.PNG"]]
I think you will agree that this save file is a lot more informative than the default, even if it isn't as huge as RPG Maker's! By default, Another RPG Engine's custom save display shows the major area (set through the {{{currentArea}}} story variable), the party's GP, and the current party composition and the levels of each.
How is this constructed, and what must you do if you wish to add, subtract, or modify information for your own game? Well, it turns out that's a bit tricky. The description of a save file object can't be modified through SugarCube directly. We have to devise a workaround using JavaScript.
{{{
(function() {
// Add metadata to saves.
Config.saves.onSave = function (save) {
var active = save.state.history[save.state.index];
save.metadata = {
area : active.variables.currentArea,
party : active.variables.puppets.filter(function (p) { return p !== null; }),
money : active.variables.currency
};
};
// Create save description from metadata.
function createSaveDescription(metadata) {
var desc = `<div class="save-desc monospace"><div class="save-left">`;
if (metadata.area !== undefined) { desc += `<span class="save-area">${metadata.area}</span>`; }
if (metadata.money !== undefined) { desc += `<span class="save-money">${setup.CURRENCY_NAME}: ${metadata.money}</span>`; }
desc += `</div>`;
if (metadata.party !== undefined) {
desc += `<span class="save-party" style="min-width:${setup.PORTRAIT_SIZE*setup.PARTY_SIZE}px;">`;
metadata.party.forEach(function(puppet) {
desc += `<div style="display:inline-flex; flex-direction:column;"><span class="save-puppet" style="width:${setup.PORTRAIT_SIZE}px; height:${setup.PORTRAIT_SIZE}px;">`;
if (setup.SHOW_PORTRAITS === true && puppet.portrait !== undefined) {
desc += `<img src="${puppet.portrait}" />`;
} else {
desc += `${puppet.portrait}`;
}
desc += `</span>LV ${puppet.level}</div>`;
});
desc += '</span>';
}
return desc+`</div>`;
}
// Modify native Save dialog descriptions upon opening the dialog.
if (true) {
$(document).on(':dialogopening', function () {
if ($('#ui-dialog-body').hasClass('saves')) {
$('#ui-dialog-body.saves tr').each(function (_, el) {
var $tr = $(el);
var $load = $tr.find('button.load:not([disabled])');
if ($load.length === 0) {
return;
}
var slot = $load.attr('id').split('-')[2];
var save = slot === 'auto'
? Save.autosave.get()
: Save.slots.get(Number(slot));
if (save !== null && typeof(save.metadata) == 'object') {
$tr.find('td>div:first-child')
.empty()
.append(createSaveDescription(save.metadata));
}
});
// Refresh the custom display when deleting saves
$('#ui-dialog-body.saves button.delete').on('click', function () {
$(document).trigger(':dialogopening');
});
}
});
}
})();
}}}
That is the purpose of this function, found in {{{saves-modifier.js}}} and authored by TheMadExile. There are three components here, but we will only cover the first two. (The third function, in a nutshell, alters the content of the HTML element holding the save description when the save menu is opened. You shouldn't need to touch it unless you really know what you're doing.)
{{{
Config.saves.onSave = function (save) {
var active = save.state.history[save.state.index];
save.metadata = {
area : active.variables.currentArea,
party : active.variables.puppets.filter(function (p) { return p !== null; }),
money : active.variables.currency
};
};
}}}
The first thing we need to do is record the data we want to use in our display. Fortunately, SugarCube saves have a property just for this kind of thing, called {{{metadata}}}. This function extracts the variables from the currently active moment of the story (so, the most current passage) and allows us to store them however we wish as properties of {{{metadata}}} when a save is made. By default, we store information for the area, the active party, and the player's money.
{{{
function createSaveDescription(metadata) {
var desc = `<div class="save-desc monospace"><div class="save-left">`;
if (metadata.area !== undefined) { desc += `<span class="save-area">${metadata.area}</span>`; }
if (metadata.money !== undefined) { desc += `<span class="save-money">${setup.CURRENCY_NAME}: ${metadata.money}</span>`; }
desc += `</div>`;
if (metadata.party !== undefined) {
desc += `<span class="save-party" style="min-width:${setup.PORTRAIT_SIZE*setup.PARTY_SIZE}px;">`;
metadata.party.forEach(function(puppet) {
desc += `<div style="display:inline-flex; flex-direction:column;"><span class="save-puppet" style="width:${setup.PORTRAIT_SIZE}px; height:${setup.PORTRAIT_SIZE}px;">`;
if (setup.SHOW_PORTRAITS === true && puppet.portrait !== undefined) {
desc += `<img src="${puppet.portrait}" />`;
} else {
desc += `${puppet.portrait}`;
}
desc += `</span>LV ${puppet.level}</div>`;
});
desc += '</span>';
}
return desc+`</div>`;
}
}}}
{{{createSaveDescription}}} is where we construct the custom description itself, based on the metadata from the save. You <i>could</i> just plunk all the data in raw, but that wouldn't look very nice. We use HTML elements to construct the description piece by piece, and then return the full HTML code when we're done.
You can see the CSS used to make the save display presentable in {{{saves desc.css}}}. Some of it's pretty complicated, so don't mess with it unless you know what you're doing! In particular, the default party display looks ugly because the default characters don't have portraits or other associated art <del>because I can't draw</del>. You should probably use portraits or other representative images for your party members; this is better than using their names, because with images, you can ensure a consistent size that won't break the display. (By default, the space provided for each character is 50 pixels square; you can change this through the {{{PORTRAIT_SIZE}}} variable in StoryInit.) You may also want to set the caps on currency and level with care, as there is currently no handler for overflow if the numbers get too large and exceed their expected dimensions.
<h3 id="animationsDoc">Animations</h3>
[img[setup.ImagePath + "documentation/AnimExample.gif"]]
Animations give a sense of life to the otherwise still world of a text-based video game. They're a great way to make your game more exciting and engaging. In this section, you'll learn how they're implemented in this engine, and how you can customize your own animations.
<h4 id="animations.design">Designing the Animations</h4>
The animations are coded through CSS. In addition to letting you customize the properties of HTML elements, CSS can also change properties dynamically with animations. The animations used in the default engine are modified from animations provided by <a href="https://animate.style/" target="_blank">animate.css</a>, a free set of excellent CSS animations. The complete set of animations is included in the {{{stylesheets}}} folder, with the modified animations in {{{custom animations.css}}}. (Redefining the same animation name with different properties will overwrite it, provided the version you want in the final product comes last in compilation order.) In particular, I used modified forms of the "headShake", "slideOutUp", and "slideOutDown" animations.
For more information on how to design your own animations, see <a href="https://www.w3schools.com/css/css3_animations.asp" target="_blank">this guide by w3schools</a>.
<h4 id="animations.setup">Setup</h4>
To begin with, you're going to need to see how the animations are set up. This is done in the {{{action effects}}} passage.
{{{
<<set _queue = new Set()>>
}}}
To begin with, we define this variable before we run any action code. It is a temporary variable, so we don't have to worry about it accidentally bleeding information over to the next passage. We define it as a {{{Set}}} object; this is an iterable container object like an array, but with the distinction that <b>all its elements must be unique</b> -- it cannot contain duplicates. If you try to add a duplicate element, nothing will happen. This is exactly what we want for the {{{_queue}}} object, because we will be using it to store every character we want to animate. A {{{Set}}} allows us to run general code for adding a character to the {{{_queue}}} for everything that would trigger an animation, without duplicating them if e.g. they were hit multiple times by the same attack.
{{{
<<if $ANIMATIONS === true && _queue.size > 0>>
<div class="actors animationContainer">
<<set _animationActive = true>>
<<for _i, _p range _queue>>
<div style="position:relative">
<div @id="'box'+_i" style="display:inline-block">
<<capture _p>>
<<liveblock>>
<<actorBox _p "" "simplified">>
<</liveblock>>
<</capture>>
</div>
<<for _x, _m range _p.battleMsg>>
<div @id="'dmg'+_i+'-'+_x" class="dmgPopup">
<<print _m.content>>
</div>
<</for>>
</div>
<</for>>
</div>
<</if>>
}}}
Then comes the animation block. Note that this code is run <i>after</i> the action code is run, even though in the final passage it's displayed first; this is due to the {{{order}}} property, which allows flexbox elements to be displayed in a different order than in the code. In this case, we need to run the action code first to collect data that we need for the animations.
{{{
<<if $ANIMATIONS === true && _queue.size > 0>>
}}}
First, we only run this if the user has allowed it through the {{{ANIMATIONS}}} variable (defined in {{{StoryInit}}}), and if there are characters in the {{{_queue}}}. Some actions don't target anyone, in which case there's no need for an animation.
{{{
<div class="actors animationContainer">
<<set _animationActive = true>>
}}}
We want the animation lineup to look similar to the character lineup in the normal battle display, so we use the same container class, but with an additional {{{animationContainer}}} class that will allow us to specify custom details in the stylesheet. We also set an {{{_animationActive}}} flag, which we will use later.
{{{
<<for _i, _p range _queue>>
<div style="position:relative">
<div @id="'box'+_i" style="display:inline-block">
<<capture _p>>
<<liveblock>>
<<actorBox _p "" "simplified">>
<</liveblock>>
<</capture>>
</div>
<<for _x, _m range _p.battleMsg>>
<div @id="'dmg'+_i+'-'+_x" class="dmgPopup">
<<print _m.content>>
</div>
<</for>>
</div>
<</for>>
}}}
The contents are generated similarly to <a class="noExternal" href="#actorlistDoc">the actor list</a>, generating an {{{<<actorBox>>}}} for every character in the {{{_queue}}}. We also run a second loop over the character's {{{battleMsg}}} attribute, which is an array containing information for every message we want to pop up during the animation, and generate another element for each message. (These elements are invisible by default, as a property of the {{{dmgPopup}}} class. We will make them visible only when the animation begins.) Note that we need to tag each one of these elements with a unique ID, as we will need to be able to reference them later to trigger the animation.
{{{
<<liveblock>>
<<actorBox _p "" "simplified">>
<</liveblock>>
}}}
We also wrap each {{{<<actorBox>>}}} in a {{{<<liveblock>>}}} macro so it can be updated dynamically during the animation.
{{{
<<set target().battleMsg.push({shake: true, type: "damage", content:$dmg})>>
}}}
Messages themselves are set up when the action executes; for example, here is a line of code from the {{{<<echoDamage>>}}} widget. (This is why we need the action code to execute first.) Each message contains three important attributes that determine how it will appear: the type (damage, healing, effect, etc.), the content (the text that will display in the popup), and whether or not it makes the actor box shake. By default, negative effects (taking damage, gaining ailments, losing buffs) all make the box shake, to simulate being hit by an attack. Positive effects (healing, gaining buffs, losing ailments) just display the popup with no shake.
<h4 id="animations.execution">Execution</h4>
The control code for animations is called in a special passage, {{{PassageDone}}}. {{{PassageDone}}} executes only after the passage has finished rendering, which is important for ensuring there are no glitches in animation delays due to passage load times. The animation controller itself is stored in the file "battle animations".
{{{
<<set _animationsToComplete = 0>>
<<set _animationsComplete = 0>>
}}}
The first thing we do is define the {{{_animationsToComplete}}} and {{{_animationsComplete}}} variables. This allows us to track when all animations are complete, which we will need to know later.
{{{
<<timed setup.ANIM_WINDUP+"ms">>
}}}
The main animation code is wrapped in this {{{<<timed>>}}} block. This gives the player a moment to absorb the information of the new action before the animation begins, and also to offset the time taken to load the passage. {{{ANIM_WINDUP}}} is set to 750 milliseconds by default, but you can change it in {{{StoryInit}}}.
{{{
<<for _i, _p range _queue>>
<<set _idA = "#box"+_i>>
<<capture _idA, _p>>
<<for _x, _m range _p.battleMsg>>
<<set _idB = "#dmg"+_i+'-'+_x>>
<<capture _x, _idB, _m>>
<<if _m.shake>> /* if true, box shakes */
<<set _time = (_x*(setup.ANIM_DURATION+400))+"ms">>
<<timed _time>>
<<run animateCSS(_idA,"headShake",setup.ANIM_DURATION+"ms")>>
<<include "popup animation">>
<</timed>>
<<else>>
<<set _time = (_x*500)+"ms">>
<<timed _time>>
<<include "popup animation">>
<</timed>>
<</if>>
<</capture>>
<</for>>
<</capture>>
<<set _animationsToComplete += _p.battleMsg.length>>
<<set _p.battleMsg = []>>
<</for>>
}}}
The animations are activated through two nested loops: Once over every character in the {{{_queue}}} variable, and then over every message in those characters' {{{battleMsg}}} attributes. At the end of every loop, we add the number of messages the character carried to a running total in {{{_animationsToComplete}}}, and reset the {{{battleMsg}}} attribute to an empty array so no messages are erroneously carried over into the next turn.
Now, to get the timing right on the animations, we have to think carefully. This entire block of code, like everything else, will be executed as soon as the passage loads at the speed of the computer's processor (very fast). If we simply trigger the animation within each loop, they will all execute at once, making all the messages overlap each other in a sloppy mess.
{{{
<<set _time = (_x*500)+"ms">>
<<timed _time>>
}}}
To stagger the messages, we need to use {{{<<timed>>}}} macros, but here is the key: the code for the <i>creation</i> of the {{{<<timed>>}}} macros executes at the same time as everything else; that is, near-instantaneously. If we use the same delay for every {{{<<timed>>}}} macro, they'll all go off at the same time, defeating the purpose of staggering them. To space them out properly, we need to increment the delay with every loop by multiplying the index variable (the current iteration number) by some constant. This will near-simultaneously create a {{{<<timed>>}}} macro with a delay of 500ms, then another of 1000ms, and so on, creating {{{<<timed>>}}} macros that trigger 500ms apart.
Note that for messages that make the actor shake, we use a longer delay time. This is because we have to make sure the shake animation completes before we trigger it again. If an animation is reactivated before it completes, it may behave strangely or simply not trigger again.
{{{
<<set _animation = "slideOutUp">>
<<switch _m.type>>
<<case "damage">>
<<set _p.hp -= _m.content>>
<<case "healing">>
<<run $(_idB).addClass("green")>>
<<set _p.hp += _m.content>>
<<case "block">>
<<run $(_idB).addClass("small")>>
<<case "addEffect">>
<<run $(_idB).addClass("small")>>
<<run $(_idB).addClass("stat-raised")>>
<<case "removeEffect">>
<<run $(_idB).addClass("small")>>
<<run $(_idB).addClass("maroon")>>
<<set _animation = "slideOutDown">>
<</switch>>
<<update>>
<<if typeof(_m.mod) == "string">>
<<run $(_idB).addClass(_m.mod)>>
<</if>>
<<set _animationsComplete++>>
<<run animateCSS(_idB,_animation,setup.DMG_DURATION+"ms")>>
}}}
Here is the code for the popup animation specifically. The code reads the {{{type}}} attribute of the message to determine its behavior, adds any classes specified in the message's {{{mod}}} attribute (if it has one), and increments {{{_animationsComplete}}}. By default, healing numbers are colored green, added effects are colored blue, and removed effects are colored maroon. (Elemental weaknesses and resistances are also marked with special colors, which you can see in {{{<<echoDamage>>}}}.)
<b>Note that HP adjustments for damage and healing are only done here and not in the action code itself.</b> This is to create the appearance of the actor box dynamically updating to reflect the results of each hit. However, because this code takes place inside a {{{<<timed>>}}} block, <b>it will not execute if the player leaves the passage before the animation triggers</b>.
<h4 id="animations.disabling">Disabling the Continue Button</h4>
To ensure the above glitch doesn't happen, we need to prevent the player from moving to the next passage until all the animations complete.
{{{
<<if $("#continue") !== undefined>>
<<script>>
$("#continue .macro-button").each((index, element) => {
element.disabled = true;
});
<</script>>
<</if>>
}}}
That's done through this code, run at the start of the animation activator. This script uses jQuery to select the continue button (which we have tagged with the {{{#continue}}} ID) and disables it, preventing it from being clicked by the player.
However, we can't just end it there; otherwise, the player will never be able to click the continue button, and they'll be stuck!
{{{
function handleAnimationEnd() {
node.classList.remove(`${prefix}animated`, animationName);
if ($('#continue') !== undefined && temporary().animationsComplete >= temporary().animationsToComplete) {
$("#continue .macro-button").each((index, element) => {
element.disabled = false;
});
}
resolve('Animation ended');
}
}}}
We re-activate the continue button with this function, defined within the {{{animateCSS}}} function and called at the end of every animation. If {{{_animationsComplete}}} matches the number of {{{_animationsToComplete}}}, we run the same code in reverse, re-enabling the continue button.
For actions that don't generate any animations, the animation activator won't be called, so the continue button won't be disabled in the first place.
<h2 id="setup">Setting up the battle</h2>
{{{
<<set $B = {target: null, subject: null, actor: null, turn: "player", turnCounter: 0, enemyTurns: 0, phase: "command", embargo: 0, event: false, surrender: false, kills: [], specialdeath: [], XPreward: 0, moneyReward: 0, destination: previous()}>>
<<callEncounter $scenario>>
<<refreshPuppets>>
<<if typeof($B.style) == 'string'>>
<<addclass "html" $B.style>>
<</if>>
<<set $B.playerBars = []>>
<<for _i, _p range $puppets>>
<<set _id = 'p'+_i>>
<<run $B.playerBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation false>>
<<sizing 100%>>
<</newmeter>>
<</for>>
<<set $B.enemyBars = []>>
<<for _i, _e range $enemies>>
<<set _id = 'e'+_i>>
<<run $B.enemyBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.ENEMY_BAR_COLOR>>
<<animation false>>
<<sizing 100%>>
<</newmeter>>
<</for>>
<<include "custom battle preparation">>
<<set $inbattle to true>>
<<set $stScreen = 1>>
<<if $B.ambush>>
<<set $B.turn = "enemy">>
<<goto "enemy phase">>
<<else>>
<<goto "Battle!">>
<</if>>
}}}
The "Preparation" passage, shown here, is responsible for cleaning up variables and getting everything ready for the battle. You will want to have something like this to make sure every battle starts the way you want it to, without any loose flags or variables messing things up. The most important thing here is the initialization of the {{{$B}}} variable, which stands for "battle controller". {{{$B}}} will be used to handle every flag and variable we need to keep track of during battle. Tying these variables to a single object makes keeping track of them easy -- if we want to clear and reset our variables (such as when starting a new battle), we can just create a brand new {{{$B}}}, instead of having to keep track of whether or not we've reset every single variable that can come up during battle.
You should pay attention to the {{{<<callEncounter>>}}} line, immediately after the initialization of {{{$B}}}. This references a widget defined in the "Database: Encounters" passage.
{{{
<<widget "callEncounter">>
<<switch $args[0]>>
<<case "tutorial">>
/* Dummy enemies for the tutorial. */
<<set $enemies to [new Enemy(), new Enemy(), new Enemy()]>>
<<run $enemies[0].effects.push(new Effect("Stunned",1,0))>>
<<run $enemies[1].effects.push(new Effect("Pain",3,10))>>
<<run $enemies[2].effects.push(new Effect("SPC Boost",3,10))>>
}}}
In "Database: Encounters" we can see how the {{{<<callEncounter>>}}} widget is used to create the enemy parties. They are created as arrays of either "Puppet" or "Enemy" objects, which is useful because it will allow us to easily iterate over them later. I recommend creating your own "Database: Encounters" passage as a separate twee file and using it to overwrite the default passage.
<b>NOTE: Though you may be tempted, DO NOT use the {{{fill()}}} function to fill enemy arrays.</b> {{{fill()}}} fills an array with exact copies of fill element, which means all the enemies will have the same ID. This makes it impossible to accurately recall the target and subject; see <a class="noExternal" href="#chain">the {{{<<chain>>}}} widget</a> for details.
{{{
<<refreshPuppets>>
}}}
Next, we reset puppets to their resting states with {{{<<refreshPuppets>>}}}. For more details, go <a class="noExternal" href="#refreshPuppets">here</a>.
{{{
<<set $B.playerBars = []>>
<<for _i, _p range $puppets>>
<<set _id = 'p'+_i>>
<<run $B.playerBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation false>>
<<sizing 100%>>
<</newmeter>>
<</for>>
<<set $B.enemyBars = []>>
<<for _i, _e range $enemies>>
<<set _id = 'e'+_i>>
<<run $B.enemyBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.ENEMY_BAR_COLOR>>
<<animation false>>
<<sizing 100%>>
<</newmeter>>
<</for>>
}}}
Next, we define the characters' health bars. Due to the unusual ways meters function and are stored in the game data, these have to be made on battle initialization, as each one requires a unique name. Because parties can be any size, we can't know how many meters to create in advance. Instead, we have to key them to the number of players and enemies in the current battle.
By default, I've set the health bars to have no color transition, no animation, and to span the full width of their container. You can edit any of these features if you like. The color variables are set in StoryInit, though this also allows for you to give characters unique colors; just define a {{{color}}} attribute or some such and pass it to the {{{<<colors>>}}} macro.
<b>Note that meters are not stateful.</b> This means that they do not persist across save game loads or browser refreshes. You may have noticed that if you refresh the page during the battle, the display will become a mess of error messages telling you the health meters don't exist; this is why. Unless you plan to remake the meters at the start of every passage, you should not allow saving during battles.
After this, custom preparation code is called. In the default engine, this is just the {{{<<restock>>}}} widget, which replenishes items.
<h2 id="skeleton">How the battle passages work</h2>
The battle screen that the player sees isn't just a single passage. It's many smaller passages (including the "Preparation" passage you just read about) all linked together and called at various points. You can see all of them in the "Battle Phases" file in the "passages" folder. You can open that file to follow along.
The file begins by defining a lot of the widgets used in battle. We'll return to these; you can skip over them for now.
The "Battle!" passage is the passage that ties everything together, but it doesn't contain much code, other than {{{<<include>>}}}ing the relevant passages in the right spots. Note how it is structured, however:
{{{
<span id="status">
<<include status>>
</span>
<span id="content">
<span id="actorlist">
<<include "actorlist">>
</span>
<div id="phase">
</div>
</span>
}}}
The two major components of the battle display, the character display and the phase of battle, are wrapped in HTML elements that give them unique IDs. This is necessary if we want to change them using macros.
<h3 id="auto-end">Auto-endturn</h3>
{{{
<<set _doneCount = 0>>
<<for _puppet range puppets()>>
<<if (_puppet.isDone || _puppet.dead || _puppet.noact)>>
<<set _doneCount++>>
<</if>>
<</for>>
<<if !($B.victory || $B.defeat || _specialmsg)>>
<<if $AUTO_ENDTURN === true && _doneCount == puppets().length>>
<<endturn>>
<<elseif setup.TURN_EXCHANGE === true && $B.enemyTurns > 0>>
<<set $B.phase = null>>
<<goto "enemy phase">>
<</if>>
<</if>>
}}}
In addition to the main content, there is also this handler for the automatic turn ending feature, which can be enabled in the Settings menu. If it's enabled, we automatically end the player's turn after all characters have acted. To accomplish this, we intialize a {{{_doneCount}}} variable to 0, then run a loop over {{{$puppets}}}, incrementing {{{_doneCount}}} whenever we find a puppet who has acted or cannot act (due to being defeated or under a hold effect). If {{{_doneCount}}} then equals the length of {{{$puppets}}}, all characters have acted and we end the turn.
{{{
<<if $AUTO_ENDTURN === true && _doneCount == puppets().length>>
}}}
The feature is controlled by this conditional. {{{$AUTO_ENDTURN}}} is controlled by the Settings menu.
{{{
<<if !($B.victory || $B.defeat || _specialmsg)>>
}}}
Note that we also perform this check before we consider the auto-forwarder at all. Why? Well, Twine has a strange quirk: If it is forced to execute multiple {{{<<goto>>}}} operations on the same page, they will all execute simultaneously, ending with the player being forwarded to the last passage specified by a {{{<<goto>>}}}. Since the victory and defeat handlers also forward the player to another passage (see <a class="noExternal" href="#victorycheck">{{{<<victorycheck>>}}}</a>), we need to prevent that from happening. Otherwise, players will be forwarded to the victory passage and then immediately back to the battle screen, causing all sorts of problems! You can also set a {{{_specialmsg}}} flag in case you need to use any other passage forwarding, such as to a special scene using {{{<<specialcheck>>}}}.
{{{
<<elseif setup.TURN_EXCHANGE === true && $B.enemyTurns > 0>>
<<set $B.phase = null>>
<<goto "enemy phase">>
<</if>>
}}}
A related feature is "turn exchange", which is set in {{{StoryInit}}}. This will force a shift to the enemy phase if there are any {{{enemyTurns}}} stored -- in practice, after every player action.
<h3 id="actorlistDoc">Actor List</h3>
{{{
<<set _enemiesClass = "actors enemies">>
<<set _puppetsClass = "actors">>
<<include "battle display mods actorlist">>
<<if setup.BATTLE_GRID === true>>
<<set _enemiesClass += " grid"; _puppetsClass += " grid">>
<</if>>
<<if $B.reverse_display>>
<<set _enemiesClass += " reverse">>
<</if>>
<div @class="_enemiesClass" id="enemies">
<<include "actorlist enemies">>
</div>
<div id="battlelines">
<<include "special battle lines">>
</div>
<div id="puppets">
<<include "actorlist puppets">>
</div>
}}}
The <b>actorlist</b> passage displays the battling characters.
In the default formatting, each of the character "blocks" you see in-game are held in a CSS structure called a "flexbox". This is a useful structure for placing elements in 1-dimensional space, as it gives us a lot of control over where its elements are placed and how they're formatted. To do this, there has to be a <i>container</i> element that controls the flexbox formatting, and <i>item</i> elements that go inside the container.
The way this works in the default stylesheet is that the <i>actors</i> class is the container, and the <i>actor</i> class represents items. So before we do anything we need to make a {{{<div>}}} element with the class "actors" to create the container, and then we can add elements classed "actor" inside.
{{{
<div @class="_enemiesClass" id="enemies">
}}}
You can situationally modify the formatting of the flexbox using Twine's {{{@}}} operator. When placed before an HTML attribute, it will cause the attribute data to be read as TwineScript, allowing you to pass Twine variables as attributes. Here, we use it to input the class of the container element. If you want a certain battle scenario to display the characters in reverse order (such as in the Crystal Gems encounter in <i>Cartoon Battle</i>), you can define {{{_enemiesClass}}} as "actors reverse" to add the additional "reverse" class to the container. In all other cases, {{{_enemiesClass}}} is just defined as "actors", and the default formatting is used. <i>Note that you <b>must</b> always define something in {{{_enemiesClass}}} if you use this method, as there is no way to evaluate the attribute as partially literal and partially TwineScript.</i>
<h4 id="actorBoxDoc">Actor Box</h4>
Once the container is established, we can start adding items. This is done through a standardized widget, {{{<<actorBox>>}}}.
{{{
<<widget "actorBox">>
<<set _actor = $args[0]>>
<<if _actor instanceof Puppet>>
<<set _idx = $puppets.findIndex(function (a) { return _actor.id == a.id; })>>
<<set _nameID = "pname"+_idx>>
<<set _boxID = "p"+_idx>>
<<set _healthBar = $B.playerBars[_idx]>>
<<elseif _actor instanceof Enemy>>
<<set _idx = $enemies.findIndex(function (a) { return _actor.id == a.id })>>
<<set _nameID = "ename"+_idx>>
<<set _boxID = "e"+_idx>>
<<set _healthBar = $B.enemyBars[_idx]>>
<</if>>
<<set _class = "actor ">>
<<if $args.length > 1 && typeof($args[1]) == "string">>
<<set _class += $args[1]>>
<</if>>
}}}
The widget starts by declaring some variables: the actor itself (passed as the 0th argument to the widget), additional classes to be added to the box (the 1st argument to the widget, optional), and the actor's index in its party array, which is used to create unique anchors for hotkeys and to identify the correct health bar.
{{{
<<if _actor.caps>>
<<set _nameStyle = "text-transform:uppercase">>
<<else>>
<<set _nameStyle = "">>
<</if>>
}}}
We also define this style code if the actor's "caps" property is true, which will make their name display as uppercase. Otherwise, {{{_nameStyle}}} is blank. You can add other name modifiers here if you wish.
Then we can begin constructing the box itself. We want each block to contain the character's name, HP, HP meter, and status button. In the puppet blocks, we also want to print EN after HP. Let's go over how to do these things.
The first element we want to display is the name. This is also the most complicated element, because it also functions as a link that can perform multiple functions depending on the context of the battle.
{{{
/* Element 1: name */
<span class="actorname" @id=_nameID @style=_nameStyle>
<<if _actor.dead>>
/* Dead characters display their name and the † (dagger) symbol in place of the status button */
<<set _class = "statusbutton">>
<<if _actor.large>>
<<set _class += " absolute">>
<</if>>
<span class="dead">
_actor.fullname
<span @class=_class>†</span>
</span>
}}}
The simplest special case is if the actor is dead. In this case, all we want is for the actor's name to be grayed out and for a † (dagger) symbol to be displayed in the corner of their box. (If you can't see the symbol, your Unicode support is out of date; try updating your browser.) We want the dagger symbol to replace the status button (discussed later), so we will use the same positioning class to display it. We will also wrap the name in a "dead" class, which we can use to provide the gray color.
{{{
<<elseif _actor instanceof Puppet && $B.phase == "selection" && ndef _s && (!_actor.noact || _actor.down) && !_actor.isDone>>
/* For Puppets in the selection phase, their name becomes a link that sets them as the subject and allows the player to select their commands. */
<<capture _idx, _actor>>
<<link "_actor.name">>
<<set $B.subject = _actor>>
<<set _s = _idx>>
<<set $B.phase = "command">>
<<if setup.BATTLE_GRID === true>>
<<removeclass "#puppets" "grid">>
<</if>>
<<addclass "#enemies" "invisible">>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<<replace "#phase">><<include "commands">><</replace>>
<</link>>
<</capture>>
}}}
The next case concerns how the name should appear during the selection phase (when the player is selecting which puppet to act). What this says in English is, "If this actor is a Puppet, and the player's turn is in the selection phase, and a character has not already been selected ({{{_s}}} stands for "selection"), and this character is not under a hold effect, and this character has not already acted, turn this character's name into a link that sets this character as the subject, assign their index in the {{{$puppets}}} array to the {{{_s}}} variable, refresh the puppet actor list, and make commands appear in the {{{#phase}}} area."
To see what will happen when this link is clicked, we must take a quick trip to the "actorlist puppets" passage:
{{{
<div @class="_puppetsClass">
(...)
<<if def _s>>
<div style="position:absolute; top:0; right:0"><<backbtn>></div>
<<actorBox $B.subject>>
}}}
That's it! The puppet container will refresh to display only the character the player has just selected, allowing them to focus on them while they consider what commands to pick. Despite the complex setup code, the result is simple.
The next case determines how characters can be selected during the <a class="noExternal" href="#targetingphase">Targeting Phase</a>, and is discussed in that section.
{{{
<<else>>
/* In all other phases, just display the name as normal. */
_actor.fullname
<</if>>
}}}
Otherwise, the actor's name is displayed as plain text, no fluff.
{{{
/* Element 2: status button */
<<if !_actor.dead>>
/* only display status button if character is not dead */
<<set _class = "statusbutton">>
<<if _actor.large>>
<<set _class += " absolute">>
<</if>>
<<capture _actor>><span @class=_class><<status _actor>></span><</capture>>
<</if>>
}}}
Then we display the status button, which is much simpler, as the code for the button itself is offloaded to the {{{<<status>>}}} widget. All this code has to do is make sure the button appears correctly; by default, only if the character is not dead. (We also have to add the additional "absolute" class if the actor box is large, as the large box requires the status button be positioned differently.)
{{{
/* Element 3: HP */
<<if !_actor.dead>>
<<capture _healthBar>>
<div>
HP: <<if _actor.maskhp>>???
<<else>><<print _actor.hp>><<if _actor.showMaxHP>> / _actor.maxhp<</if>>
<</if>>
</div>
(...)
<</capture>>
<</if>>
}}}
Next, we display HP (but only if the character isn't dead). There are several modifiers to this display you can set as properties of the actor object. By default, only the current HP is shown, but you can also display the maximum HP by setting the {{{showMaxHP}}} attribute. The {{{maskhp}}} attribute will instead forego this whole process and just display the HP as "???", for when you don't want the player to know the character's HP.
{{{
<<if (!_actor.maskhp && setup.SHOW_HEALTHBARS)>>
<<if setup.BATTLE_GRID === true && _actor.large>>
<div class="largehealth">
<<showmeter _healthBar `_actor.hp / _actor.maxhp`>>
</div>
<<else>>
<<showmeter _healthBar `_actor.hp / _actor.maxhp`>>
<</if>>
<</if>>
}}}
Next, we display the health bars, provided {{{setup.SHOW_HEALTHBARS}}} is set to true. The bar will stretch the entire length of the character block. For boss characters, whose block extends across the entire screen, this makes the bar very long!
{{{
/* Element 4: EN (Puppets only) */
<<if !_actor.dead && _actor instanceof Puppet && !$args.includes("simplified")>>
<div>
EN: _actor.en / _actor.maxen
</div>
<</if>>
}}}
On the next line, EN is displayed, but only for Puppets, and only if they're not dead. (This section is also omitted in "simplified" form, such as in damage animations.) By default, this display is very simple, but you can modify it as you need.
{{{
/* Element 5: Crisis points */
<<if !_actor.dead && def _s && _actor.crisis instanceof Array && _actor.crisis.length > 0 && !$args.includes("simplified")>>
/* Only display this if the character isn't currently selected (and if they have a valid Crisis in the first place) */
<<set _style = "font-size: 10pt; ">>
<<if _actor.crisisPoints >= 100>>
<<set _style += "color:red">>
<<else>>
<<set _style += "font-weight:normal">>
<</if>>
<div @style=_style>
Crisis: <<print _actor.crisisPoints>>%
</div>
<</if>>
}}}
Crisis points are displayed next, but only if the character has a valid {{{crisis}}} attribute (and isn't dead). If you don't want to use Crises in your game, just don't give the characters any, and this whole section will be ignored.
By default, Crisis points are displayed in at a smaller size than normal (because the actor box is getting pretty big by this point), and is highlighted an eye-catching red if it's at maximum.
{{{
/* Element 6: Status messages */
<<if !_actor.dead>>
<div class="noact">
<<if $args.includes("simplified")>>
<br/>
<<elseif _actor.isDone>>
Done!
<<elseif _actor.stunned>>
Stunned!
<<elseif _actor.down>>
Prone!
<<else>>
<br/>
<</if>>
</div>
<</if>>
}}}
The final element is a little notifier for when the character's turn is done, or if they're under a hold effect. In the simplified display, these messages are ignored and replaced with an empty line. Including an extra line break in the default case is not strictly necessary, but it keeps the character box dimensions consistent; if you don't include it, the box will become taller or shorter depending on if a message is displayed or not.
<h3 id="commands">Commands</h3>
[img[setup.ImagePath + "documentation/012.PNG"]]
[img[setup.ImagePath + "documentation/012a.PNG"]]
We need the player to be able to select actions and give commands to their characters to play the game. But by default, we see no commands, just the character boxes. When a character is selected, all the other characters disappear, and a command pane appears below their character box.
To show how this is done, we have to move up a step, and look at {{{actorlist puppets}}}, the container for <i>all</i> the actor boxes for the player characters.
{{{
<div @class="_puppetsClass">
(...)
<<if def _s>>
<div style="position:absolute; top:0; right:0"><<backbtn>></div>
<<actorBox $B.subject>>
<<else>>
(...)
<<for _i, _puppet range $puppets>>
<<set _puppetClass = "">>
(...)
<<if _puppet === null>>
(...)
<<else>>
<<actorBox _puppet _puppetClass>>
<</if>>
<<for>>
}}}
Recall from <a class="noExternal" href="#actorBoxDoc">the Actor Box</a> that when a Puppet's name is clicked in the selection phase, the {{{_s}}} variable is set and the puppet container is refreshed. That triggers the first {{{if}}} clause here, and displays <i>only</i> the box for the active subject (the puppet selected). (Because it is still within the flexbox container, the character box is automatically centered on the screen, which is handy.) Otherwise, we iterate over the {{{$puppets}}} array and make an {{{actorBox}}} for everyone.
{{{
<div style="position:absolute; top:0; right:0"><<backbtn>></div>
}}}
Note that this code in the selection branch is an important addition. We need to give the player the ability to go back in case they decide they want a different character to act. The {{{div}}} here is just to make sure it is placed in an aesthetically pleasing position; we can't place it inside the normal element flow, since it will go inside the flexbox and interfere with the placement of the character box. {{{position:absolute}}} allows us to control where it appears without affecting any other elements; in this case, we place it in the upper-right corner of the flexbox container. This displays it prominently near the character box, which is a good location.
However, this still doesn't show us how we get the commands to display. To answer that, recall this element from the master "Battle!" passage:
{{{
<div id="phase">
</div>
}}}
You may recall that clicking the selection link replaced this element with a passage called "commands", so let's look there:
{{{
<div class="commandcontainer">
<div class="commands">
<span id="actbtn"><<act $puppets[_s]>></span><br />
<span id="restbtn"><<rest $puppets[_s]>></span><br />
<span id="itembtn"><<items $puppets[_s]>></span><br />
<<if $B.surrender is true>>
<<spare $puppets[_s]>><br/>
<</if>>
</div>
</div>
}}}
Here, finally, are our commands. They display only if this passage has been slotted into the {{{#phase}}} element through the selection link, and they appear in a flexbox of identical dimensions that ensures they will display just under the character box. This is how we are able to display the commands only when a character has been selected.
As for the commands themselves, the mechanics required for these are a bit more complicated than a normal Twine link, so they refer to custom widgets found at the top of the "Battle Phases" file:
{{{
<<widget "act">>
<<if _puppet.down is true>>
<<if $args[0].en >= setup.struggleCost>>
<<link "Struggle">>
<<set $B.subject = $args[0]; $action = new Action("struggle"); $B.target = null>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<<else>>
<b>Exhausted!</b>
<</if>>
<<else>>
<<link "Act">>
<<set $B.subject = $args[0]>>
<<replace "#phase">><<include "actions">><</replace>>
<</link>>
<</if>>
<span class="hotkey monospace right">[Q]</span>
<</widget>>
}}}
The first branch of the if clause here may look complicated, but it's only for a special case that turns the "Act" button into a selection for a single action. The part that the player will see for most of the battle is the default {{{<<else>>}}} case, which links to the "actions" passage while assigning the current character to the {{{$B.subject}}} variable.
{{{
<<set $B.phase = "actions">>
<span class="hotkey monospace">[Q] = basic action | [W] = last action</span><br/><br/>
<<actionlist $B.subject>>
}}}
Instead of making a separate passage for every character (which would quickly get cumbersome), everything is outsourced to another widget in order to reduce clutter. This can be found the widgets section of the "Battle Phases" file, and is discussed in <a class="noExternal" href="#actions">Action Mechanics</a>.
The "Item" command works similarly.
Once you have selected an action, you are moved to the targeting phase to choose a target.
<h3 id="targetingphase">Targeting Phase</h3>
{{{
<<set $B.phase = "targeting">>
<<replace "#actorlist">><<include "actorlist">><</replace>>
<<if $B.targeting == "enemy">>
<<addclass "#puppets" "invisible">>
<<elseif $B.targeting == "ally">>
<<addclass "#enemies" "invisible">>
<</if>>
<<backbtn>>
Select a target. <span class="hotkey monospace">[hotkeys: 1-0] <<if $B.reverse_display>>[enemies are displayed in reverse order]<</if>></span><br/>
<br/>
<<if $B.targeting == "all">>
<<set _targetingEnemy = true>>
<span class="hotkey monospace" id="target_help">
[Hotkeys targeting <<if _targetingEnemy === true>>enemies<<else>>allies<</if>>. Press Shift to switch targets.]
</span>
<</if>>
}}}
The <b>targeting phase</b> passage is not much to look at in and of itself. It is basically a filler passage that gives the player a stage to select their target. For clarity, the opposite party is made invisible if the action can only target one party or another.
However, to see how it behaves under normal circumstances, let us return to {{{<<actorBox>>}}}:
{{{
<<elseif $B.phase == "targeting">>
<span class="targetnumber"><<print _idx+1>></span>
<<if ($B.targeting == "all" || (_actor instanceof Enemy && $B.targeting == "enemy") || (_actor instanceof Puppet && $B.targeting == "ally"))>>
<<if (_actor instanceof Puppet && !($B.subject.name == _actor.name && $B.noself === true)) ||
(_actor instanceof Enemy &&
((_actor.martyr || (!_martyr && !_actor.untargetable)) && ($action.ranged || guardCheck(_idx))))>>
<<capture _actor>>
<<link "_actor.fullname">>
<<set $B.target = _actor; $B.targeting = null>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<</capture>>
<<else>>
/* If invalid target, just display the name as normal. */
_actor.fullname
<</if>>
<<else>>
/* If not being targeted, just display the name as normal. */
_actor.fullname
<</if>>
}}}
This is the name-display clause we skipped over previously. During the targeting phase, this clause turns the character's name into a link that, when clicked, directs the player to the confirm phase and sets the character as the target of the ability.
(There are also several riders for special circumstances: if an enemy is flagged untargetable they are, well, untargetable, and the same applies if they are protected by someone or their team has a martyr. On the player side, there is a rider that disables the user as a valid target if {{{$B.noself}}} is {{{true}}}. This will be explained in the action section.)
<h3 id="confirmphase">Confirm Phase</h3>
{{{
<<replace "#actorlist">><</replace>>
<<if $SHOW_CONFIRM>>
<<set $B.phase = "confirm">>
<<backbtn>>
$B.subject.name will
<<if $action.name is "rest">>
<b>rest</b> this turn.
<<elseif $action.name is "struggle">>
spend setup.struggleCost Energy to get back on <<print subject().their>> feet.
<<elseif $action.item is true>>
use <<switch $action.name.first().toLowerCase()>><<case 'a' 'e' 'i' 'o' 'u'>>an<<default>>a<</switch>> $action.name.
<<elseif $action.name is "spare">>
accept the enemy's surrender.
<<else>>
use <b>$action.name</b><<if $B.target isnot null>> on <b>$B.target.name</b><</if>>.
<</if>>
[[Confirm?|action phase]]<br />
<span class="preview">
<<if $action.preview instanceof Function>>
<<print $action.preview()>>
<<else>>
<<print $action.preview>>
<</if>>
</span>
<<else>>
<<goto "action phase">>
<</if>>
}}}
The confirm phase is very simple. It is designed as a courtesy to players to prevent them from accidentally confirming the wrong action by misclicking, and to allow them to review how the action will play out. It can be turned off in the Settings menu if players don't want to make the extra click, however.
The complicated-looking if clause here is just fluff to provide different messages depending on the class of action chosen. The a/an rule in English presents an issue when describing the use of items starting with vowels. I whipped up a quick {{{<<switch>>}}} statement to address this directly, but you may be able to come up with something more robust. (Or you could just accept defeat and display it as "a(n)". If you ever wondered why game developers do that, now you know!)
The {{{preview}}} attribute is another courtesy to the player. It describes the expected effect of the action, such as pre-calculating damage. It's unique for every action, and can be examined in the action database JavaScript.
<h3 id="spellphase">Spell Phase</h3>
In the default engine, Mage can expend additional energy points to make their spells stronger. This functionality is handled in the <b>spell phase</b> and <b>spell check</b> passages.
{{{
<<backbtn>>
<<if isNaN($action.cost)>>
You need to input a number.<br/>
<<elseif $action.cost < $B.mincost>>
Spell requires at least $B.mincost Energy.<br/>
<<elseif $B.subject.en < $action.cost>>
Not enough Energy!<br/>
<</if>>
How much Energy do you want to put into $action.name? (Minimum $B.mincost)<br />
<<numberboxplus "$action.cost" $B.mincost autofocus>>
<<include "spell check">>
<</numberboxplus>><br/>
(Press Enter to confirm.)<br/>
}}}
The <b>spell phase</b> passage is shown above. A {{{<<numberboxplus>>}}} macro is used to receive player input, because it allows the player to continue just by pressing the Enter key rather than having to fuss with another confirm link. The {{{<<numberboxplus>>}}} modifies the {{{$action.cost}}} variable, is initialized to a default value of {{{$B.mincost}}} (the minimum cost for the spell), calls the "spell check" passage when Enter is pressed, and autofocuses when the page is loaded.
(Thanks to SugarCube developer TheMadExile for providing the {{{<<numberboxplus>>}}} macro, which was necessary for preserving the engine's structure.)
{{{
<<run $action.cost *= 1>>
<<if ($action.cost < $B.mincost) or ($B.subject.en < $action.cost) or isNaN($action.cost)>>
<<replace "#phase">><<include "spell phase">><</replace>>
<<else>>
<<run $action.spellMod()>>
<<if $action.phase != "confirm phase">>
<<set $B.targeting = $action.target>>
<<replace "#phase">><<include "targeting phase">><</replace>>
<<else>>
<<replace "#phase">><<include _action.phase>><</replace>>
<</if>>
<</if>>
}}}
The <b>spell check</b> passage, shown above, is not something the player should ever see. It is only for running calculations and directing the player to the correct passage.
{{{
<<run $action.cost *= 1>>
}}}
This initial {{{<<run>>}}} macro is needed to convert the {{{$action.cost}}} variable to a number, as it is passed as a string (text) when obtained through a text field.
From there, the if clause checks if the {{{$action.cost}}} value is within acceptable bounds. If the player inputted less energy than the minimum, more energy than Mage currently possesses, or something that wasn't a number, the player is pushed back to the spell phase and prompted to try again.
If the input was correct, the spell's {{{spellMod()}}} function is called to calculate adjustments for supercharging the spell. Each spell has a unique {{{spellMod()}}} that is defined in the action database. Once this is done, the player is directed to the next phase, and the action proceeds as normal.
<h3 id="actionphase">Action Phase</h3>
This is where actions take place. The "action phase" passage itself only executes standard behavior, such as variable cleanup. This will be discussed later. The details of specific actions take place in the "action effects" passage, which is {{{<<include>>}}}d in "action phase".
The actorlist passage is not included here, simply because it is difficult to update stats in real time.
Let's look at "action effects":
{{{
<<if $action.silent === true>>
<<if $action.act instanceof Function>>
<<print $action.act()>>
<<else>>
<<print $action.act>>
<</if>>
<<goto "Battle!">>
}}}
The first section handles actions that don't do anything significant enough to require a message. {{{silent}}} actions immediately forward the player back to the main battle screen after performing their function.
The "else" case here handles everything else -- that is to say, actions that <i>do</i> display a message and create text during the action phase.
How you code this section will depend on exactly how you want your actions to look. By default, this passage neatly partitions the display into use text, flavor text, and the mechanical effect of the action. Every section is given a unique ID that can be modified in the story stylesheet.
{{{
<div id="actFlavor">
<<if !($action.useText === null || ($action.useText instanceof Function && $action.useText() === null))>>
<div id="useText">
<<if $action.useText instanceof Function>>
<<print $action.useText()>>
<<else>>
<<print $action.useText>>
<</if>>
</div>
<</if>>
<<include "battle interruptions">>
<<if $action.actText !== null>>
<div id="actText">
<<if $action.actText instanceof Function>>
<<print $action.actText()>>
<<else>>
<<print $action.actText>>
<</if>>
</div>
<</if>>
</div>
}}}
First, we're going to open the "#actFlavor" {{{div}}}. This is the black-bordered text box that contains the descriptive text of the action. There are two portions to this {{{div}}}, the use text and the action text, and both get their own {{{div}}}s that you can format independently. The contents are defined as attributes of the action, and can be either normal strings, or functions (if, for instance, you want text to vary depending on certain variables). If you wish to bypass either element, you can set the corresponding attribute to {{{null}}} and the {{{div}}} will not generate.
There is also a call to {{{<<include>>}}} the "battle interruptions" passage, which can add special circumstances and additional scenes, such as one enemy protecting another from your attack.
{{{
<<if $action.act !== null>>
<div id="actEffect">
<<if def _targetingMsg>>
<<print _targetingMsg>>
<<unset _targetingMsg>>
<</if>>
<<if $action.act instanceof Function>>
<<print $action.act()>>
<<else>>
<<print $action.act>>
<</if>>
</div>
<</if>>
}}}
Then, there is a similar section for mechanical effects. Like use and act text, this is defined as an attribute of the action, usually a string of SugarCube code (which will execute as normal when {{{<<print>>}}}ed), and the element will not appear at all if the attribute is set to {{{null}}}. There is also a handler for the {{{_targetingMsg}}} variable, which handles a complicated problem discussed in <a class="noExternal" href="#targeting.basic">Standard Targeting</a>.
{{{
<<if def _OG>>
<<set $B.subject = _OG.subject; $B.target = _OG.target; $action = _OG.action>>
<<unset _OG>>
<</if>>
}}}
We also have to run this code at the very end. For special actions like counterattacks, we may want to temporarily change the subjet, target, and action, but we need to put them back afterwards so any further actions don't use the wrong variables.
This format creates a very nice, standardized, and customizable appearance for actions, but it does require actions to adhere to its rigid structure. All non-invisible actions <i>must</i> have a {{{useText}}}, {{{actText}}}, and {{{act}}} attribute, even if it's only {{{null}}}. Otherwise, you're going to be drowning in error messages when this passage displays.
We then bring this all together in the action phase:
{{{
<<set $B.phase = null>>
<span id="content">
/* Some abilities (such as AoE attacks) don't always end with the same target as the one they started with. This saves the initial target if you want to use it for something, e.g. a reaction scene. */
<<set _initialTarget = $B.target>>
/* Saves the number of kills from before the action. By comparing this to the kills array afterwards, you can identify whether or not a kill happened during the action. */
<<set _initialKills = $B.kills.length>>
/* Tracks characters who have counterattacked this action. Required to enable counters. */
<<set _counters = []>>
<<if $action.name == "spare">>
<<run $enemy_to_spare.surrender()>>
<</if>>
/* Add any bonus threat from the action. */
<<if setup.THREAT_TARGETING === true && target() instanceof Enemy && subject() instanceof Puppet>>
<<run target().threat.inc(subject().name,$action.threat)>>
<</if>>
<<include "action effects">>
/* Variable cleanup. Due to the way goto works, this will work even for invisible actions. */
/* Remember this action for the last action shortcut: */
<<if !$action.nosave && !($action.name == "struggle" || $action.name == "spare")>>
<<include "lastaction mods">>
<</if>>
/* If action has limited uses, decrement that */
<<if def $action.uses>>
<<run subject().actions.find(function(a) { return a && a.name == $action.name }).uses -= 1>>
<</if>>
/* If action has a cooldown, reset it */
<<if def $action.cd>>
<<run subject().actions.find(function(a) { return a && a.name == $action.name }).resetCD()>>
<</if>>
/* If target was an enemy (i.e. an attacking skill was used), subject is marked as attacker. (This is for enemies that target the last puppet to attack them.) */
<<if $B.target instanceof Enemy>>
<<set $B.attacker = $puppets.indexOf($B.subject)>>
<</if>>
/* isDone logic; checks for confounding factors */
<<if $action.instant>>
/* do nothing */
<<elseif $B.subject.inspired>>
<<set $B.subject.inspired = false>>
<<else>>
<<set $B.subject.isDone = true>>
<<set $B.enemyTurns++>>
<</if>>
<<if $action.oncePerTurn>>
<<set $action.used = true>>
<</if>>
/* Subtract action cost */
<<if $action instanceof ItemAction>>
<<set $B.item_used = true>>
<<if $B.subject.crafty>>
<<set $action._cost = Math.round(setup.ITEM_COST/2)>>
<<else>>
<<set $action._cost = setup.ITEM_COST>>
<</if>>
<</if>>
<<set $B.subject.en -= $action.cost>>
<<if $B.surrender && def $action.name>>
<<print $enemy_to_spare.surrenderCheck()>>
<</if>>
<br/>
<center><<button "Continue..." "Battle!">><</button>></center>
</span>
}}}
Now you can see how it all comes together in the action phase. The bits of code at the end are for tidying up. The action is saved to {{{lastAction}}}, the subject is flagged as done for the turn, and various other necessary flags are set. This code is simple in its basic structure, but a few special cases require some {{{<<if>>}}} statements, such as instant skills or the additional turn from Mage's "Inspiration" ability.
{{{
<<if $B.surrender && def $action.name>>
<<run $enemy_to_spare.surrenderCheck()>>
<</if>>
}}}
There is also functionality here for calling the {{{surrenderCheck}}} function, described in the <a class="noExternal" href="#JS.enemies">Enemy Database</a>. If the player uses an action that doesn't pass the check, the surrendering enemy will be aggro'd and cease their surrender.
<h3 id="actionqueue">Action Queue</h3>
There is additional functionality for handling special actions, such as counterattacks and Archer's Mark attacks. It is included in the {{{Widgets (Special Attacks).tw}}} file, which also handles the logic for triggering these special actions.
{{{
<center id="continue">
(...)
<<button "Continue...">>
<<if $B.actionQueue.length > 0>>
<<replace "#content" "t8n">>
<<include "Action Queue">>
<<timed 0s>><<include "animation activator">><</timed>>
<</replace>>
(...)
</center>
}}}
If you look at the "Battle Continue Button" passage, you will see this handler within the button code. This says that if the {{{actionQueue}}}, and array property of the battle controller defined at the start of the battle, has a {{{length}}} greater than 0 (i.e. it has entries), we run this code instead of forwarding the player to the next passage as normal. We {{{<<replace>>}}} the existing passage instead of moving to a new one to preserve the identity of the current passage, as the passage name is used when determining the player's next destination when the queue is finished.
The "Action Queue" passage itself looks like this:
{{{
<<if $B.actionQueue.length > 0>>
<<set _data = $B.actionQueue.shift()>>
<<set _OG = {target: $B.target, subject: $B.subject, action: $action}>>
<<set $B.subject = getActorById(_data[0]); $action = _data[1]>>
<<if !(subject().dead || subject().noact)>>
<<if $action.counter>>
<<set $B.target = _OG.subject>>
<<set _counterActive = true>>
<</if>>
<<include "action effects">><br/>
<<set _counterActive = false>>
<<else>>
<<timed 0s>><<trigger 'click' "#continue button">><</timed>>
<</if>>
<</if>>
<<include "Battle Continue Button">>
}}}
We start by {{{shift}}}ing the first entry out of the {{{actionQueue}}} and storing it in {{{_data}}}. ({{{shift}}} is an array function that extracts the first entry from an array; this both removes it from the original array, and allows us to store the entry in another variable. Because this shortens the array, we will eventually empty the {{{actionQueue}}} this way.) Then, we need to store the original target, subject, and action in a holder variable, so that we can put them back when we're done.
{{{
<<set $B.subject = getActorById(_data[0]); $action = _data[1]>>
}}}
We then need to set the new subject and action. We do this by reading the entry we got from the {{{actionQueue}}}. Entries added to the {{{actionQueue}}} are assumed to be, themselves, arrays consisting of two elements: the ID of the character to perform the action, and then the action itself.
The target is determined by the action itself. For counterattacks, there is standard code that sets the target to the original subject.
{{{
<<include "action effects">>
}}}
Note that we need to include "action effects" to run the action -- but that passage, itself, populates the {{{actionQueue}}}! If two characters with counterattacks started attacking each other, they'd keep attacking in an endless loop. To prevent that, we set the {{{_counterActive}}} flag, which prevents further counterattacks from triggering. This prevents characters from countering counters and creating an endless loop. (We need to set similar flags for every class of special action to prevent similar loops.)
{{{
<<if !(subject().dead || subject().noact)>>
(...)
<<else>>
<<timed 0s>><<trigger 'click' "#continue button">><</timed>>
}}}
Finally, we have an escape handler. If for some reason the character died or was stunned in-between triggering their counter and now, we skip straight to the next passage by simulating a click on the continue button. (Note that we have to wrap this code in a {{{<<timed>>}}} block because it references an element on the page, which won't exist until the whole page has finished rendering.)
<h3 id="endOfRound">End of Round</h3>
{{{
<span id="status">
<<include status>>
</span>
<span id="content">
<<if $B.turn eq "player">>
/* If the turn reads "player", it's because the enemy round just finished. Run end of turn for enemies. */
/* If it's a new player turn, it's a "true" new round, so need to update and reset controller variables. */
<<set $B.turnCounter++; $B.embargo--; $B.enemyTurns = 0; $B.actions_this_turn = {}>>
<<endOfRound $enemies>>
<<newTurn $puppets>>
<<if _message>>
<<button "Continue..." "Battle!">><</button>>
<<else>>
<<goto "Battle!">>
<</if>>
<<elseif $B.turn eq "enemy">>
/* If the turn reads "enemy", the player turn just finished. Run end of turn for player. */
<<endOfRound $puppets>>
<<if _message>>
<br/><<button "Continue..." "enemy phase">><</button>>
<<else>>
<<goto "enemy phase">>
<</if>>
<</if>>
</span>
}}}
As you can see, the functionality of the <b>end of round</b> passage is mostly outsourced to a widget, which can be found at the top of the "Battle Phases" file.
{{{
<<widget "endOfRound">>
<<for _actor range $args[0].filter(function (a) { return a !== null; })>>
<<set _actor.isDone = false>>
<<if _actor instanceof Enemy && def _actor._respawn && _actor.dead>>
<<set _actor.respawn-->>
<<if _actor.respawn <= 0>>
<<set _actor.dead = false; _actor.hp = _actor.maxhp; _actor.resetRespawn()>>
<<set _message = true>>
<div id="actFlavor">
<<print _actor.respawnMessage>>
</div>
<br/>
<</if>>
<</if>>
<<for _effect range _actor.effects>>
/* DoT check */
<<if _effect.dot is true>>
<<set _message to true>>
<<set $dmg = _effect.damage(_actor)>>
<div id="actFlavor">
<<print _effect.msg(_actor)>>
</div>
<div id="actEffect">
<<echoDamage _actor "indirect" "nocalc">>
</div>
<br/>
<</if>>
<</for>>
/* decrementor */
<<decayMessage _actor.effects false>>
<</for>>
<</widget>>
}}}
The main purpose of the end-of-round widget is to handle status effects. Status effect durations are decremented and damage-over-time effects inflict their damage. This is accomplished through two for loops: one across the party (which party is specified in the argument given to the widget), and one across the effects of the current character. If a damage-over-time effect is found, its damage is calculated its damage message is printed.
For effect decrement, we use another widget, {{{<<decayMessage>>}}}. (This code is compartmentalized because it is reused in the top-of-round code; see {{{newTurn}}}, below.)
{{{
<<widget "decayMessage">>
<<set _topDec = Boolean($args[1])>>
<<set _decayMsg = "">>
<<for _effect range $args[0].filter(function (eff) { return eff.topDec == temporary().topDec })>>
<<set _m = _effect.decay(_actor)>>
<<if _m.length > 1>>
<<set _decayMsg += _m>>
<</if>>
<</for>>
<<if _decayMsg.length > 0>>
<div id="actEffect">
<<print _decayMsg>>
</div>
<br/>
<</if>>
<</widget>>
}}}
We call the {{{decay}}} method function on each of the character's effects, which decrements the effect's duration by 1 and removes it if it hits 0. If the effect is removed, {{{decay}}} will return the removal message, which we will store in temporary variable {{{_m}}} and add to an aggregate text string, {{{_decayMsg}}}. When all effects are finished, we print the aggregate {{{_decayMsg}}}, providing it contains anything.
{{{
<<if _m.length > 1>>
<<set _decayMsg += _m>>
<</if>>
}}}
Note this check here. Some effects produce an empty decay message, which is not meant to be displayed to the player. We don't want to add these strings to {{{_decayMsg}}}, so we need to check that {{{_m}}} is longer than 1 character. The reason the check is <i>strictly greater than</i> 1 as opposed to <i>greater than or equal to</i> 1 is because the {{{decay}}} function automatically adds a newline character, {{{\n}}}, to any decay message it receives, even if it's empty.
{{{
<<if _message>>
<<button "Continue..." "Battle!">><</button>>
<<else>>
<<goto "Battle!">>
<</if>>
}}}
Finally, we use the {{{_message}}} flag is used to ensure the player only lingers on the "end of round" passage if necessary. If no messages were generated, {{{_message}}} will remain false, and the player will be pushed to the next passage automatically. This is done to create a smoother player experience. (The {{{decay}}} function will automatically flip {{{_message}}} to {{{true}}} if it receives a non-empty decay message.)
There is a corresponding widget called at the top of a round: {{{<<newTurn>>}}}.
{{{
<<widget "newTurn">>
<<for _actor range $args[0].filter(function (a) { return a !== null; })>>
<<set _actor.isDone to false>>
<<if !_actor.dead>>
<<run _actor.regenHP()>>
<<run _actor._retaliations.refill()>>
<</if>>
<<if _actor instanceof Puppet>>
/* Puppet-exclusive tasks:
decrement respawn (enemy respawn only decremented at end of round)
refresh used actions */
<<if _actor.dead && def _actor._respawn>>
<<set _actor.respawn-->>
<<if _actor.respawn <= 0>>
<<set _actor.dead = false; _actor.hp = Math.round(_actor.maxhp * setup.RESPAWN_HP); _actor.resetRespawn()>>
<<set _message to true>>
<div id="actFlavor">
<<print _actor.respawnMessage>>
</div>
<br/>
<</if>>
<</if>>
<<for _action range _actor.actions.filter(function (act) { return act.used === true })>>
<<run _action.used = false>>
<</for>>
<</if>>
<<decayMessage _actor.effects true>>
<</for>>
<<include "custom newTurn">>
<</widget>>
}}}
This refreshes everyone's {{{isDone}}} flag, regenerates HP and retaliatiations, advances respawn counters, and decrements top-of-round status effects. Actions that can only be used once per turn, such as Mage's "Sacrifice" ability, are also reset so they can be used again. (In the default engine, EN regeneration is handled in "custom newTurn", so you can easily remove it if you don't want to use EN.)
<h3 id="enemyphase">Enemy Phase</h3>
The enemy turn. This passage is complicated, because it must essentially incorporate all the events of the previous passages at once.
{{{
<<if (deadCount() == puppets().length)>>
/* If all puppets are dead, it's game over -- no point in finishing this passage, just let victorycheck do its thing. */
}}}
The very first thing we have to do is actually to check if there's any point in running the enemy turn at all! It is possible for the enemies to defeat the player in the middle of their turn; it would be pointless for them to keep acting after that, as the player has already lost. In this case, we run no code in this passage, and just let the defeat handler resolve the battle.
{{{
<<if $B.ambush>>
<<set $B.ambush = false; $B.turn = "enemy">>
<center style="font-weight:bold">AMBUSHED!</center><br/>
<</if>>
}}}
If we <i>are</i> executing the enemy turn, first we have a handler for ambushes. Normally, battles start on the player's turn, but if the {{{ambush}}} property is flagged, the battle starts here. We display a message informing the player of this fact, and unset {{{ambush}}}.
{{{
<<set _enemy = enemies().sort(function(a,b) { return a.priority - b.priority; })
.find(function (e) { return e && !e.isDone && !e.dead })>>
}}}
We then need to find the active enemy. We can't just iterate over the whole enemy party with a loop, because we don't want every enemy's turn to display at once. Instead, we have to pick a single enemy by searching the array. First, we sort the enemy party in ascending {{{priority}}} value; then, we run a {{{find}}} function searching for an enemy that is defined, not done, and not dead. Because {{{find}}} stops at the first element that satisfies its conditions, and because we have already sorted the array, this will ensure enemies act in the proper priority order.
{{{
<<if _enemy instanceof Enemy>>
(...)
<<else>>
<<set $B.turn = "player">>
<<goto "end of round">>
<</if>>
}}}
However, we can only go ahead if that operation <i>did</i> find a valid enemy! If all the enemies have acted, {{{find}}} will fail and return {{{undefined}}}. In this case, we should end the enemy turn and forward the player to the next round.
{{{
<<set $B.subject = _enemy>>
<<set $B.target = null>>
<<if _enemy.isFirstAction>>
<<newTurn `[_enemy]`>>
<</if>>
<<set _enemy.isDone = true>>
}}}
But if all is well, we will run {{{<<newTurn>>}}} for the enemy, which provides the same functions as it did for the player characters. (Note that {{{<<newTurn>>}}} only works with arrays, so we need to place the enemy object within an array when passing it to the widget.) However, we need to manually set {{{isDone}}} to {{{true}}}, because if you recall, {{{<<newTurn>>}}} sets {{{isDone}}} to {{{false}}}. The enemy also becomes the active subject.
{{{
<<if !_enemy.fakedeath>>
(...)
<<else>>
<<goto "enemy phase">>
}}}
Then we have to check this clause before proceeding. The {{{fakedeath}}} property essentially skips the enemy's turn as if they were dead, but still allows the above new-round effects to occur.
{{{
<<if _enemy.noact>>
<<set _effect = _enemy.effects.sort(function(a,b) { return a.priority - b.priority; })
.find(function (e) { return e && e.holdAction instanceof Function })>>
/* Sorts effects by priority and returns the first hold effect (one with a holdAction) */
<<run console.assert(_effect !== undefined,`ERROR in enemy phase: ${_enemy.name} has noact but no hold effect`)>>
<<set $action = _effect.holdAction()>>
<<include "action effects">><br/>
<<include "custom end of action effects">>
}}}
The enemy is then checked against hold effects. If they have one (or more), their effects are sorted by {{{priority}}}, then the first effect with a working {{{holdAction}}} property is found. {{{holdAction()}}} is then run, which fills {{{$action}}} with a "dummy" action that just contains {{{actText}}} informing the player that the enemy is under said effect. (The {{{priority}}} sort allows you to have a heirarchy of hold effects; for example, by default, enemies cannot right themselves from Knocked Down if they are also Stunned.)
{{{
<<else>>
<<if _enemy.isFirstAction>>
<<run _enemy.decCD()>>
<</if>>
<<set _counters = []>>
<<set $action = null>>
<<run _enemy.actions()>>
<<run $B.actionsThisTurn[_enemy.id].push($action.name)>>
<<if !_targetfail>>
<<include "action effects">><br/>
<</if>>
(...)
<<run $B.enemyTurns -= 1>>
<</if>>
}}}
Only in the default {{{<<else>>}}} clause, if the enemy passes all these checks, does enemy action occur. The {{{actionsThisTurn}}} object can be used to track which actions the enemy has already used on their turn, if you want them to modify their behavior based on previous actions. (Note that we must populate it with the enemy's <i>ID</i>, not the enemy's name, because while names can be duplicated, IDs are unique.)
The details of the {{{actions}}} function can be found in the {{{database-enemies}}} JavaScript file, which is discussed <a class="noExternal" href="#JS.enemies">here</a>. Setting {{{$action}}} to {{{null}}} is necessary to ensure the function works correctly; otherwise the enemy will think they've already selected an action.
{{{
<<if !_targetfail>>
}}}
This check is necessary in case the enemy's attack fails to find a target. (See <a class="noExternal" href="#targeting.basic">Targeting</a>.) If this happens and the action is allowed to play out, glitches can occur.
{{{
<center id="continue">
<<button "Continue...">>
<<if $B.enemyTurns > 0 || $B.turn == "enemy">>
<<goto "enemy phase">>
<<else>>
<<goto "Battle!">>
<</if>>
<</button>>
</center>
}}}
Finally, at the very end of the passage, we generate a button to allow the player to continue. It has branching behavior depending on the {{{enemyTurns}}} counter: If there are enemy turns remaining (or if it's the enemy's turn, in which case they all act at once), we refresh this passage, which allows the next enemy to be selected and act. (Remember that if all the enemies have acted, we will forward the player to the end of round passage instead through an earlier handler.) Otherwise, the player is taken back to the main "Battle!" screen and returned to their turn.
<h4 id="enemyphase.multiActions">Enemies with Multiple Actions</h4>
Some enemies can act multiple times per turn, as determined by their {{{noAttacks}}} (short for "number of attacks") attribute. For this, we need some special handlers.
{{{
get isFirstAction () {
return (V().B.actionsThisTurn[this.id] instanceof Array && V().B.actionsThisTurn[this.id].length == 0);
}
}}}
You might recall seeing this property used in some of the previous checks. It returns {{{true}}} if the enemy's {{{noAttacks}}} is at maximum; that is, if they haven't taken any actions yet. This is useful, because we want to keep running the same action code for the enemy until they're out of actions, but there's some code, like {{{<<newTurn>>}}}, we only want to run once.
{{{
<<if $action.fullround === true>>
<<run _enemy.noAttacks = 0>>
<<elseif !$action.instant>>
<<run _enemy.noAttacks-->>
<</if>>
<<if _enemy.noAttacks > 0>>
<<set _enemy.isDone = false>>
<<else>>
<<run $B.enemyTurns -= 1>>
<</if>>
}}}
After the enemy's action is executed, we run some checks against their {{{noAttacks}}}. Firstly, if the action had the {{{fullround}}} attribute, it is considered to end the enemy's turn no matter how many actions they had remaining, so we set {{{noAttacks}}} to 0. Otherwise we'll reduce {{{noAttacks}}} by 1, unless the action was instant. Then, we'll only remove the enemy's turn from the {{{enemyTurns}}} pool <i>if they have no actions remaining</i> -- if {{{noAttacks}}} is greater than 0, {{{isDone}}} is set to {{{false}}}, allowing the enemy to be selected again. In this way, we allow the enemy to keep acting until it's out of actions. ({{{noAttacks}}} is refreshed at the end of the round, starting the cycle anew.)
<h3 id="advanceturn">Advancing Turns</h3>
There is a feature added in version 1.12 that modifies the behavior of the "End Turn" button.
{{{
<<set $B.subject.isDone = true>>
<<set $B.enemyTurns++>>
(...)
<<run _enemy.decCD(); $B.enemyTurns-->>
(...)
<<for _e, _enemy range $enemies.sort(function(a,b) { return a.priority - b.priority; })>>
<<if !($B.enemyTurns > 0 || $B.turn == "enemy")>>
<<break>>
}}}
Notice this {{{enemyTurns}}} variable. We skipped over it in prior discussions, but let's bring it all together now. You can see through this code (from the action and enemy phases) that {{{enemyTurns}}} is incremented when a player character finishes their turn, and decremented when an enemy performs their turn. If {{{enemyTurns}}} drops to 0 while the turn is still the player's, the {{{break}}} statement within {{{if !($B.enemyTurns > 0 || $B.turn == "enemy")}}} will be executed, and the enemy actions loop will stop prematurely.
This allows the player to space out the enemy turns, instead of having to take all of their attacks at once. This isn't too big of a deal in <i>Cartoon Battle</i>, but players might appreciate it in a system where they have lower HP totals than the enemies and might get taken out if a single character is swarmed by all enemies.
However, we still need to account for the possibility that the enemy and player parties are unequal. If the enemy has 4 characters and you have only 3, what happens when you use all of your turns and have only given the enemy 3? This is the purpose the {{{if $B.turn == "enemy"}}} clause. If the player's turn has completely ended and the round has shifted to the enemies, they bypass the {{{enemyTurns}}} check and all remaining enemies can act freely.
{{{
<<if $B.enemyTurns == 0 || _doneCount == puppets().length>>
<<button "END TURN">>
<<endturn>>
<</button>>
<<else>>
<<button "ADVANCE TURN">>
<<set $B.phase = null>>
<<goto "enemy phase">>
<</button>>
<</if>>
(...)
<<widget "endturn">>
<<set $B.turn = "enemy"; $B.phase = null>>
<<goto "end of round">>
<</widget>>
(...)
<<button "Continue...">>
<<if $B.turn == "enemy">>
<<set $B.turn = "player">>
<<goto "end of round">>
<<else>>
<<goto "Battle!">>
<</if>>
<</button>>
}}}
But that raises another question: How do we determine when the player's turn has "ended"? That's handled through the first block of code here, which generates the "End Turn" button for the player. If there are no enemy turns left or if all puppets have acted, the button says "END TURN", sets the {{{turn}}} property to "enemy", and forwards the player to the "end of round" passage. Otherwise, the button says "ADVANCE TURN", and only forwards us to the enemy passage without modifying the {{{turn}}} property. From the previous code, you can see this means only a number of enemies equal to the {{{enemyTurns}}} variable will act. The enemy phase's "Continue" button has its own form of this: If the turn belongs to the enemy, we switch the {{{turn}}} property back around and go to "end of round" to finish the round; otherwise, we go back to the player's side of things.
{{{
<<if _start == $B.enemyTurns>>
<<set $B.turn = "player">>
<<goto "end of round">>
<</if>>
}}}
There's also this little check, which is just for convenience. If {{{enemyTurns}}} doesn't change -- so, no enemies act -- this passage will be blank except for the "Continue" button, which looks weird. There's no need for the player to linger on the passage in this case, so we just automatically forward them to the end of the round.
<h3 id="victorydefeat">Victory and Defeat</h3>
So, what happens when the battle ends? There is a standard {{{Victory}}} passage that the player is forwarded to if they win (see <a class="noExternal" href="#victorycheck">{{{<<victorycheck>>}}}</a>). Let's look at it:
{{{
<<set _XPBars = []; _animationTime = "1s">>
}}}
The first thing is to initialize the array of our experience bars and their animation time (defaulting to 1 second).
{{{
<div class="actors">
<<for _i, _puppet range $puppets>>
<<set _id = 'p'+_i>>
<<run _XPBars.push(_id)>>
<<set _value = Math.clamp((_puppet.xp - _puppet.XPtoNext(_puppet.level - 1)) / (_puppet.XPtoNext() - _puppet.XPtoNext(_puppet.level - 1)),0,1)>>
<<newmeter _id _value>>
<<colors cyan cyan black>>
<<animation _animationTime linear>>
<<sizing 100%>>
<</newmeter>>
}}}
We iterate over our {{{$puppets}}} and generate XP bars similarly to how we generated health bars in <a class="noExternal" href="#actorlist">actorlist</a>. The starting value of the bars is a little complicated to calculate: We are looking not at the ratio of total XP to the XP of the next level (which both account for <i>all</i> the XP a puppet has acquired since level 1), but how much <i>more</i> XP the puppet has to get to reach the next level. To get that, we need to calibrate for the current level. To track the puppet's current progress, we subtract the puppet's current XP from the {{{XPtoNext}}} of the <i>previous</i> level, and mark the "goal" as the {{{XPtoNext}}} of their current level minus their previous. We also have to clamp this ratio between 0 and 1 to prevent possible overflow.
That was probably confusing to follow, so here's a concrete example: Say the puppet has just reached level 3, and has 3000 XP in total. According to their {{{XPtoNext}}} function, they will reach level 4 when they have 5000 XP <i>in total</i>. However, in relative terms, they only need 2000 more XP to reach the next level. If we just set the meter's progress to {{{XP / XPtoNext()}}}, that will give us {{{3000 / 5000}}}, starting the meter at 60% filled! But if we subtract the XP requirement for the previous level (3000) from both the current and target XP values, we get {{{0 / 2000}}} -- completely unfilled, as it should be when starting at the next level. If the puppet gains 1000 XP, the meter's progress will be {{{1000 / 2000}}} -- halfway filled.
{{{
<<animation _animationTime linear>>
}}}
Unlike the health bars, these meters <i>are</i> animated, with a time set by {{{_animationTime}}}. You can change this if you like.
{{{
<div class="actor victory">
<center style="text-transform:uppercase; font-weight:bold">_puppet.name</center>
LEVEL <span @id="'lv'+_i">_puppet.level</span>
<<showmeter _XPBars[_i] _value>>
<center @id="'lvlupmsg'+_i"> </center>
</div>
}}}
With our calculations done, we can display the actual meter. The puppet is displayed in a standard {{{actor}}} box, with a few modifications from the {{{victory}}} class. (The most important one is a {{{margin-bottom: 1em}}} attribute, which spaces out the boxes more nicely if they run onto a second line.) The puppet's current level is displayed within a {{{<span>}}} marked with an {{{id}}} keyed to the puppet's index, then the XP bar, and then a placeholder for a level up message, again marked with an {{{id}}} keyed to the puppet's index so we can reference it later.
{{{
<<set _multiplier = 1>>
<<set _puppet.xp += Math.round($B.XPreward * _multiplier)>>
<<set _value = Math.clamp((_puppet.xp - _puppet.XPtoNext(_puppet.level - 1)) / (_puppet.XPtoNext() - _puppet.XPtoNext(_puppet.level - 1)),0,1)>>
<<updatemeter _XPBars[_i] _value>>
}}}
Having initialized the XP bar, now we can update it. Each puppet's XP is incremented by the {{{XPreward}}} accumulated throughout the battle (see <a class="noExternal" href="#deathcheck">{{{<<deathcheck>>}}}</a>), the new value of the meter is calculated the same way as before, and the meter is updated with the new value. (This will run the update animation. If you have no animation specified, the XP meters will immediately start at their updated values from the player's perspective. Note that there's no delay on this, so the meters will start filling immediately; if you want to space it out, you'll have to place this code in a separate {{{<<timed>>}}} block.)
We also include a variable, {{{_multipler}}}, that you can use to account for any special modifiers on XP. Perhaps you have a piece of equipment that lets you gain XP faster, or a status effect that renders you unable to gain XP.
{{{
<<set $currency += $B.moneyReward>>
<center style="margin-top:1em;"><div style="display:inline-block; width:50%; font-size:20pt; text-align:left"><<print setup.CURRENCY_NAME>> <span style="float:right">$B.moneyReward</span></div></center>
}}}
After that's done for all puppets, we increment the player's currency variable by the battle's {{{moneyReward}}}, and display a message informing the player of this fact. (If you have more complicated rules for handling currency, such as every character having their own purse, you'll need to do something different.)
{{{
<<set _itemDrops = []>>
<<for _enemy range $B.kills>>
<<for _item, _chance range _enemy.itemDrops>>
/* Assumes itemDrops is an object with property names of items corresponding to a number between 1 and 100 equal to the % chance of the item being dropped. */
<<if typeof(_item) == 'string' && typeof(_chance) == 'number'>>
<<set _r = random(1,100)>>
<<if _r <= _chance>>
<<run inv().addItem(_item); _itemDrops.push(_item)>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if _itemDrops.length > 0>>
<center style="margin-top:1em;">
<div style="font-size:20pt; font-weight:bold">DROPS:</div>
<span class="itembox" style="width:30%">
<<for _item range _itemDrops>>
<b><<print _item>></b><br/>
<</for>>
</span>
</center>
<</if>>
}}}
Then comes item drops. We'll loop over the {{{kills}}} array and extract the {{{itemDrops}}} table from each enemy. If the table has been constructed correctly (a list of item names as properties pointing to percentile values), we roll a d100 to see if the item was dropped by the enemy. If so, we add it to the player's inventory and to the {{{_itemDrops}}} array, which we iterate over next to display the information to the player.
Note that this code allows the same enemy to drop multiple items, as the loop over {{{itemDrops}}} is not stopped if a drop is found. You will need to change this code if you want different behavior.
Note also that this prints every item individually, even if they're duplicates. This could get cumbersome if you plan to have large party sizes and/or for enemies to drop many items, and it may be worthwhile to figure out a way to lump duplicate items together onto one line for this circumstance.
{{{
<<timed _animationTime>>
<<for _i, _puppet range $puppets>>
<<if _puppet.xp >= _puppet.XPtoNext()>>
<<set _id = "#lv"+_i>>
<<replace _id>><span style="font-weight:bold"><<print (_puppet.level+1)>></span><</replace>>
<<set _id = "#lvlupmsg"+_i>>
<<replace _id>><span style="font-weight:bold; color:cyan">LEVEL UP!</span><</replace>>
<</if>>
<</for>>
<<levelcheck>>
<<timed 3s>>
<<goto "Level Check">>
<</timed>>
<</timed>>
}}}
We then execute this {{{<<timed>>}}} block. It waits for the XP bars' animations to finish, then if the puppet leveled up, it replaces the {{{<span>}}}s we marked earlier with a "LEVEL UP!" message and an update to reflect the puppet's new level.
After another {{{<<timed>>}}} block, to give the player a chance to read and process these messages, the player is forwarded to the "Level Check" passage:
{{{
<<if $LevelUps.length > 0>>
<<set _p = $LevelUps.shift()>>
<center><<LevelUp _p>></center>
<<else>>
<<endofbattle>>
<<goto $B.destination>>
<</if>>
}}}
If any puppets were added to the {{{$LevelUps}}} queue, they're {{{shift}}}ed out of the list and leveled up. The "Level Up" passage will keep directing the player back here until all level ups are complete, at which point the battle will end and the player will be forwarded to the passage specified by {{{$B.destination}}}. (By default, this is the passage immediately before the battle started, but unique destinations can be specified in the encounter definitions.)
Note that all of this assumes you are using a standard RPG model with XP, levels, and currency rewards after every battle. If you aren't, you may need to change this passage.
<h2 id="actions">Action Mechanics</h2>
{{{
<<widget "actionlist">>
/* Widget for display of player actions. */
/* Individual action widgets take cost, damage multiplier, and other relevant variables as arguments so they can be displayed in the description. Values are given in "Database: Actions". */
<<run console.assert($args.length > 0 && ($args[0] instanceof Puppet),"ERROR in actionlist: no Puppet")>>
<<set _char = $args[0]>>
(...)
<<set _actions = _char.actions>>
<<for _action range _actions>>
<<if $inbattle && !_action.passive>>
(...)
<div @class="_actionClass">
<<capture _action>>
<<mouseover>>
<span class="actionName">
<<if actionStandardCheck(_action)>>
_action.name<<if (def _action.cd && _action.cd !== 0)>> <span class="cooldowndisplay">[CD <<print _action.cd>>]</span><</if>>
<<elseif actionLockCheck(_action)>>
<del>_action.name</del> <span class="dizzy">Dizzy!</span>
<<elseif actionHPCheck(_action)>>
<del>_action.name</del> <span class="dizzy">Not enough HP!</span>
<<elseif actionElementCheck(_action)>>
<del>_action.name</del> <span class="dizzy">No element.</span>
<<else>>
<<link "_action.name">>
<<actionLink>>
<</link>>
<</if>>
</span>
<<onmouseover>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>>
<<actionInfo _action _char "full">>
<</replace>>
<</if>>
<<onmouseout>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>>
<<include "action box default">>
<</replace>>
<</if>>
<</mouseover>>
<</capture>>
<<if $COMPRESSED_ACTIONS === true && $inbattle>>
<<actionInfo _action _char "no name">>
<<else>>
<<actionInfo _action _char "no name" "full">>
<</if>>
</div>
<<elseif !$inbattle>>
<div class="actionDisplay uncompressed">
<<actionInfo _action _char "full">>
</div>
<</if>>
<</for>>
<</widget>>
}}}
The construction of actions is previously discussed in <a class="noExternal" href="#JS.actions">the action database</a>. They are displayed in-game through this widget, found in the "Battle Phases" file. This widget iterates across the "actions" array of a character with a for loop to create the links you use to select actions in the game.
{{{
<<capture _action>>
}}}
Note that before we do anything, we need to {{{<<capture>>}}} the current {{{_action}}} for our code. Links are activated by the user after the page has already rendered, and thus after the loop is finished. If we don't capture our iterator variable, every link will behave based on the data from the last action found by the loop. {{{<<capture>>}}} holds variables within its bounds, so the variable can be used correctly by the link.
<h3 id="actions1">Special Cases</h3>
The default {{{<<else>>}}} case, which determines how things normally look, is listed last, so special cases come first in the code.
{{{
<<if $inbattle && !_action.passive>>
}}}
The first clause handles passive abilities. We don't want to display passive abilities in battle, so we will only run the code for the display if the action's {{{passive}}} property is not {{{true}}}.
We then run into this clause:
{{{
<<if actionStandardCheck(_action)>>
}}}
which refers to this function in {{{1_support_functions.js}}}:
{{{
window.actionStandardCheck = function actionStandardCheck (action) {
return (temporary().char.en < action.cost)
|| (typeof(action.uses) == "number" && action.uses < 1)
|| (typeof(action.cd) == "number" && action.cd !== 0)
|| (action.used === true)
|| (action.crisis && temporary().char.crisisPoints < 100);
}
}}}
This is for checking the most obvious cases: When the character has insufficient energy, uses, or cooldown to perform an action, obviously the player shouldn't be able to choose the action. This will also disable "once per turn" actions such as Mage's "Sacrifice" if they have already been used. (Note that we have to check if the action has a defined {{{uses}}} property, because not all of them do, and unexpected behavior could occur if we try to use a comparison operator with an {{{undefined}}} value.)
If the character can't pay the cost, the action's name is displayed as plain text with no special properties. (If you want it to display some other way, you can add your own HTML and CSS to give it a different appearance.)
{{{
<<if (def _action.cd && _action.cd !== 0)>> <span class="cooldowndisplay">[CD <<print _action.cd>>]</span><</if>>
}}}
We also display a cooldown message next to the action's name if it's on cooldown.
{{{
window.actionLockCheck = function actionLockCheck (action) {
return (temporary().char.dizzy && !action.basic);
}
}}}
The next clause handles the "Dizzy" effect. Most RPGs have a "skill lock" effect that prevents characters from using anything but basic attacks; the example you are probably most familiar with is the "Silence" effect from the <i>Final Fantasy</i> series of games. Since <i>Cartoon Battle</i> had a lot of physical skills, I chose to use the name "Dizzy" instead, implying that the characters could not perform complex actions while disoriented.
{{{
<<elseif actionLockCheck(_action)>>
<del>_action.name</del> <span class="dizzy">Dizzy!</span>
}}}
What this clause says is that if the character's "dizzy" flag is marked true, the action name is crossed out next to a "Dizzy!" message, again with no clickable link -- unless the action is flagged "basic", in which case the {{{!_action.basic}}} conditional will fail and this section will be bypassed.
Despite being functionally identical to the first clause, I chose to make this a separate clause to convey more information to the player. If the action looks the same regardless of whether the character is dizzy or out of energy, the player might be confused about why they're deactivated. Making a different display for each helps the player understand what's going on.
{{{
window.actionHPCheck = function actionHPCheck (action) {
return (action.hpcost && temporary().char.hp <= action.hpcost);
}
(...)
<<elseif actionHPCheck(_action)>>
<del>_action.name</del> <span class="dizzy">Not enough HP!</span>
}}}
The next case deactivates the link for HP-consuming skills if the character does not have the requisite HP, with a unique message again for clarity for the player. Some games will let characters kill themselves with HP-consuming skills, but I chose to be nice and prevent this possibility in <i>Cartoon Battle</i>. (If the {{{hpcost}}} attribute is undefined this conditional will simply be skipped, so you don't have to worry about defining it for every action.)
{{{
window.actionElementCheck = function actionElementCheck (action) {
return (action.needsPriorElement && typeof(temporary().char.lastUsed) !== "string");
}
(...)
<<elseif actionElementCheck(_action)>>
<del>_action.name</del> <span class="dizzy">No element.</span>
}}}
Lastly, there's this very specific check I had to make for Artist's abilities. If the action needs to read the user's last used element and no such property exists, the action needs to be disabled.
<h3 id="actions2">The Functional Link</h3>
So what happens when everything's in order, and the player can select the action? That's handled by the final {{{else}}} clause:
{{{
<<else>>
<<link "_action.name">>
<<actionLink>>
<</link>>
<</if>>
}}}
The functionality here is offloaded to a widget:
{{{
<<widget "actionLink">>
<<if $args.length > 0>>
<<set _action = $args[0]>>
<</if>>
<<if !(actionStandardCheck(_action) || actionLockCheck(_action) || actionHPCheck(_action) || actionElementCheck(_action))>>
<<unset _s>>
<<if _action.passagejump>>
<<goto _action.phase>>
<<else>>
<<set $action = clone(_action)>>
<<if !$action.nosave>>
<<set $B.subject.lastAction = $action>>
<</if>>
<<set $B.targeting = _action.target>>
<<set $B.noself = _action.noself>>
<<if _action.phase is "confirm phase">>
<<set $B.target = null>>
<<set $B.targeting = null>>
<<elseif _action.phase is "spell phase">>
<<set $B.mincost = _action.cost>>
<<set $B.targeting = null>>
<</if>>
<<replace "#actorlist">><<include "actorlist">><</replace>>
<<replace "#phase">><<include _action.phase>><</replace>>
<</if>>
<</if>>
<</widget>>
}}}
This code assigns a <i>clone</i> of the action to the permanent {{{$action}}} variable, or in other words, a copy. As discussed in <a class="noExternal" href="#chain">the {{{<<chain>>}}} widget</a>, what is done to one object variable is done to all variables that reference the same object. So if we were to manipulate {{{$action}}} (such as by modifying the {{{cost}}} attribute for variable-cost spells), we would run the risk of passing on that change to the permanent action in the character's array as well. The clone avoids this problem.
Various other variable adjustments are made, depending on if the action has special properties. If the action is a spell, the link also stores the minimum cost, which will be used later in the spell phase.
After processing is finished, the link forwards the player to the next phase, based on what the action's {{{phase}}} property specified.
<h3 id="actions3">Info Display</h3>
We could stop here, and we'd technically still have a functioning game. However, players wouldn't know what any the actions do! We need to display that information somehow. This is handled through the {{{actionInfo}}} widget.
{{{
<<widget "actionInfo">>
<<run console.assert($args.length > 0 && ($args[0] instanceof Action),"ERROR in actionInfo: no arguments passed")>>
<<if $args[1] instanceof Puppet>>
<<set _char = $args[1]>>
<</if>>
<<set _act = $args[0]>>
<<if !$args.includes("no name")>><b><<print _act.name>></b><</if>>
<<if def _act.uses>> <span class="usedisplay">(Uses: <<print _act.uses>>/<<print _act.maxUses>>)</span><</if>>
<span style="float:right; margin-left:0.5em;">
<<if $args.includes("full")>>
<<if !$inbattle && _char.defaultAction.name === _act.name>>
<b>[Default]</b>
<</if>>
<<if _act.crisis>>
<b>[Crisis]</b>
<</if>>
<<if _act.basic>>
[Basic]
<</if>>
<<if _act.instant>>
[Instant]
<</if>>
<<if _act.passive>>
[Passive]
<</if>>
<</if>>
<<if _act instanceof ItemAction>>
x<<print _v.stock>>
<<else>>
<<if !_act.passive && Number.isInteger(_act.cost) && ((!_act.crisis && _act.cost >= 0) || (_act.crisis && _act > 0))>>
<<print _act.cost>><<if _act.phase === "spell phase">>+<</if>> EN
<</if>>
<</if>>
</span>
<<if !$inbattle && !_act.noDefault && _char.defaultAction.name !== _act.name>>
<div style="font-size:9pt">
<<capture _act>>
<<link "[Set as default]">>
<<set _char.defaultAction = _act>>
<<replace "#menuActionList">><<actionlist _display>><</replace>>
<</link>>
<</capture>>
</div>
<</if>>
<<if $args.includes("full")>>
<<if _act instanceof ItemAction>>
<<set _act = new Item(_act.name)>>
<</if>>
<div><<print _act.info>></div>
<<if _act.desc !== null>><div class="actdesc"><<print _act.desc>></div><</if>>
<</if>>
<</widget>>
}}}
This widget is used for display of actions both in and out of battle, so there are many branches to account for. We will be focusing just on the in-battle branches.
There is additional branching functionality depending on if we want the widget to display the full information of the action, in which case the action is passed the argument "full", or if we only want to display the minimum cruical information.
{{{
<<if def _act.uses>> <span class="usedisplay">(Uses: <<print _act.uses>>/<<print _act.maxUses>>)</span><</if>>
}}}
The action's uses are always displayed after the action name if it's a limited-use action, which we can check with {{{if def _act.uses}}}.
{{{
<span style="float:right; margin-left:0.5em;">
<<if $args.includes("full")>>
(...)
<<if _act.basic>>
[Basic]
<</if>>
<<if _act.instant>>
[Instant]
<</if>>
<<if _act.passive>>
[Passive]
<</if>>
<</if>>
}}}
We then create an element that is aligned to the right edge of the container. If we're displaying the full action details, we use it to display special tags such as whether the action is basic or instant.
{{{
<<if _act instanceof ItemAction>>
x<<print _v.stock>>
<<else>>
<<if !_act.passive && _act.cost !== null>><<print _act.cost>><<if _act.phase is "spell phase">>+<</if>> EN<</if>>
<</if>>
}}}
We will always display the cost of the action (or the stock if it's a usable item). If the action points to the spell phase, it has a variable cost, which is noted with a "+" marker after the cost number.
{{{
<<if $args.includes("full")>>
<<if _act instanceof ItemAction>>
<<set _act = new Item(_act.name)>>
<</if>>
<div><<print _act.info>></div>
<<if _act.desc !== null>><div class="actdesc"><<print _act.desc>></div><</if>>
<</if>>
}}}
We then display the action's info and description, but only if we are displaying full information. For usable items, these properties are tied to the item rather than the action, so we need to create an Item object with the same name to correctly reference the data.
Why do we need all these handlers? Because the action list can be displayed two different ways, depending on the player's preference: compressed or uncompressed.
{{{
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "compressed">>
<<else>>
<<set _actionClass = "">>
<</if>>
<div id="actionlist" @class="_actionClass">
<<actionlist $B.subject>>
</div>
<<if $COMPRESSED_ACTIONS === true>>
<br/>
<div id="actionInfo">
</div>
<</if>>
(...)
<<if $COMPRESSED_ACTIONS === true && $inbattle>>
<<actionInfo _action "no name">>
<<else>>
<<actionInfo _action "no name" "full">>
<br/>
<</if>>
}}}
The first block of code is in the "actions" passage. If actions are compressed, the action list's containing element will gain a new class that changes its display to a two-column grid, allowing it to display twice as many actions in the same space. We then display only minimal information for the action rather than a full description to save space.
However, players using the compressed display still need a way to see the full information somehow. How can we do that?
{{{
<<mouseover>>
(...)
<<onmouseover>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>>
<<actionInfo _action "full">>
<</replace>>
<</if>>
<<onmouseout>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>><</replace>>
<</if>>
<</mouseover>>
}}}
The answer: with the {{{mouseover}}} macro. This code is wrapped around the action name in {{{actionlist}}}, and will make the action name a mouse-sensitive element. When the player mouses over the action name, we will populate the {{{#actionInfo}}} element with the action's full information using {{{actionInfo}}}. When the mouse leaves the element, the info box will become blank again. (This is why we needed to wrap the {{{capture}}} macro around the whole name display code, rather than just the link.)
<h3 id="crossbow">How does Rogue's crossbow work?</h3>
[img[setup.ImagePath + "documentation/054.PNG"]]
[img[setup.ImagePath + "documentation/055.PNG"]]
When Rogue uses the Crossbow ability, it's replaced by a new ability, Reload, which must be used before Crossbow can be used again. How is this accomplished?
{{{
"Crossbow": {
"cost": 0,
"weight": 1,
"basic": true,
"pierce": true,
"info": function (action) {return `Attack with a weight of ${action.weight} and ignore defense. Needs reloading after use.`},
"desc": `Ah, the marvel of modern technology: while other fools tire themselves out swinging those heavy weapons, Rogue can send death flying through the air with just a twitch of the finger.`,
"useText": null,
"actText": function () {
return `${subject().name} fires their crossbow with a <i>twang</i>.`;
},
"act": function () {
return `<<echoDamage>>`+
`<<find "$B.subject.actions" "name" "\'Crossbow\'">>`+
`<<set $B.subject.actions[_pos] = new Action("Reload")>>`;
},
},
"Reload": {
"cost": 2,
"phase": "confirm phase",
"basic": true,
"invisible": true,
"info": function (action) {return `Reload crossbow.`},
"desc": `...Of course, crossbows also take an age and a half to reload.`,
"preview": "",
"act": function () {
var x = subject().actions.find(function(a) { return a && a.name == "Reload" });
subject().actions[subject().actions.indexOf(x)] = new Action("Crossbow");
}
}
}}}
To begin with, observe that these are both defined as separate actions in the action database. This allows us to easily create either a "Crossbow" or "Reload" action as needed. Then, look at the behavior for the Reload action:
{{{
var x = subject().actions.find(function(a) { return a && a.name == "Reload" });
subject().actions[subject().actions.indexOf(x)] = new Action("Crossbow");
}}}
This is a little clunky, but it allows us to locate exactly where the "Reload" action is in the {{{actions}}} array so we can replace it. The position of Crossbow and Reload are fixed in Rogue's actions, but for another character it might be different, or we might want to allow the player to move the order around. This ensures we'll always target the right action.
The principles behind this mechanic are useful if you want to make a game like <i>Final Fantasy VII</i> where skills can be swapped out like equipment.
<h3 id="items">Items</h3>
Items are coded almost exactly the same way as actions. The only real difference is that the link reads the item's {{{action}}} attribute, which is defined exactly the same way as a regular action.
<h3 id="delayed">Delayed Attacks</h3>
You may want to have a delayed attack that requires one or more turns of "charge time" before being executed, during which the character can't do anything else. You can see an example of this in the Jump ability of <i>Final Fantasy</i>'s Dragoon class, or in the banishing and daybreak spells featured in the Marceline fight in <i>Cartoon Battle</i>.
These are bit tricky to implement in this engine, because you will need to keep track of the target between turns, and <a class="noExternal" href="#chain">Twine can't preserve object references.</a> I recommend capturing a unique property of the target instead, such as their name or ID, and saving it to a property of the subject used exclusively for delayed attack targets. Then, on the turn it is to execute, you can find the target again by plugging that attribute into a {{{find}}} function.
In the default engine, there is no initiative order, so the delayed attack should happen immediately at the start of the turn. Run a check to see if any characters are executing a delayed attack (set a flag on the setup action so you can track this), then set the subject, target, and action to whatever is appropriate, and immediately forward the player to the action phase with {{{<<goto "action phase">>}}}. However, you should <b>not</b> use {{{<<newTurn>>}}} for this purpose, as that widget also contains a {{{<<goto>>}}}. Because {{{<<goto>>}}} does not end the passage and all code after it continues to execute, placing a {{{<<goto>>}}} before the end of {{{<<newTurn>>}}} will cause {{{<<goto>>}}} commands to pile up and execute simultaneously, preventing the player from seeing the effects of the action. <b>Use {{{<<specialcheck>>}}} for this purpose instead.</b> Though it is called continuously, so long as you remember to unset the "executing delayed attack" flag, it will only trigger the delayed attack once. The continuous call is also advantageous in the case that multiple characters are using delayed attacks simultaneously, as opposed to {{{<<newTurn>>}}}, which is only called once.
In a system with initiative order, this is simpler, as you would simply execute the delayed attack on the character's turn.
<h2 id="damage">Calculating and applying damage</h2>
Because these widgets are complicated, they are placed in their own twee file, "Damage and Formulas".
The widgets for calculating and displaying damage are separate, due to the need to pre-calculate damage for the {{{<<preview>>}}} widget.
<h3 id="damage.1">Calculating damage</h3>
{{{<<damageCalc>>}}}'s control flow is as follows:
{{{
<<set _w = $action.weight>>
(...)
<<set _atk = ($B.subject.get("Attack") * (1 - $action.useSpecial)) + ($B.subject.get("Special") * $action.useSpecial)>>
/* Piercing? */
<<if $action.pierce>>
<<set _def = Math.min(_target.get("Defense"),setup.MIN_STAT)>>
<<else>>
<<set _def = _target.get("Defense")>>
<</if>>
}}}
First, extract some attributes into shorter variable names, for convenience. The attack factor is a weighted average of the subject's Attack and Special stats, determined by the action's {{{useSpecial}}} attribute, which is a number between 0 and 1. An action with a {{{useSpecial}}} of 1 would be completely dependent on Special (as the Attack factor would be multiplied by 0), while an action with {{{useSpecial}}} of 0.25 would only have 25% of its power based on Special.
If the attack is piercing, tracked through the {{{$action.pierce}}} flag, we set {{{_def}}} to a minimum value. Pay attention to what that minimum value is; it can be 0 in a subtractive system, but you're going to run into division by zero errors if you try that in a divisive system.
{{{
<<if !$action.formula>>
<<switch setup.formula>>
<<case "subtractive">>
<<set $dmg to ((setup.base+setup.damper*_atk)*_w)-setup.damper*_def>>
<<case "subtractive lumped">>
<<set $dmg to (setup.base+setup.damper*(_atk-_def))*_w>>
<<case "rpgmaker">>
<<set $dmg to (_atk*4-_def*2)*setup.damper*_w>>
<<case "divisive">>
<<set $dmg to (setup.base*(_atk/_def))*_w>>
<<default>>
/* add your own here! */
<</switch>>
<<else>>
<<set $dmg = $action.formula()>>
<</if>>
}}}
Then damage is actually calculated through a call to "damageCalc formula". I've provided functionality for 4 possible formulas, though it's set to "subtractive" by default. The construction behind these damage formulas is discussed in [[the design page|Design]]. You may want to change it for your game. Actions can also have a unique formula, which will be used instead if one is detected.
{{{
<<set _factor = 1; _flatFactor = 0>>
<<if typeof($action.element) == "string">>
<<set _factor = _target.getElement($action.element,"percent")>>
<<set _flatFactor = _target.getElement($action.element,"flat")>>
<<elseif $action.element instanceof Array>>
<<set _factor = 0>>
<<if setup.AVERAGE_ELEMENTS === true>>
<<for _v range $action.element>>
<<set _factor += _target.getElement(_v,"percent")>>
<<set _flatFactor += _target.getElement(_v,"flat")>>
<</for>>
<<set _factor = _factor*1.0/$action.element.length>>
<<set _flatFactor = _flatFactor*1.0/$action.element.length>>
<<else>>
<<for _v range $action.element>>
<<set _factor = Math.max(_target.getElement(_v,"percent"),_factor)>>
<<set _flatFactor = Math.min(_target.getElement(_v,"flat"),_flatFactor)>>
<</for>>
<</if>>
<</if>>
<<set _baseDmg = $dmg>>
<<set $dmg *= _factor; $dmg -= _flatFactor>>
}}}
We then check for elemental effects. If the action has an element, we'll use it to find the target's affinity value for that element, then apply it to the damage value. (This is checked for both flat additive values and percent-based values.) If there wasn't an element or the target has no associated resistance value {{{_factor}}} will be undefined and cause an error when we attempt to apply it to {{{$dmg}}}, so we start by initializing it to a base value that doesn't affect anything.
There is also a second branch here for the handling of attacks with multiple elements. If you assign an array instead of a string to the action's {{{element}}} property, you can give the action multiple elements that will all be used to calculate the final result. This can work in one of two ways, depending on how you set the {{{AVERAGE_ELEMENTS}}} variable.
{{{
<<if setup.AVERAGE_ELEMENTS === true>>
<<for _v range $action.element>>
<<set _factor += _target.getElement(_v,"percent")>>
<<set _flatFactor += _target.getElement(_v,"flat")>>
<</for>>
<<set _factor = _factor*1.0/$action.element.length>>
<<set _flatFactor = _flatFactor*1.0/$action.element.length>>
}}}
If {{{AVERAGE_ELEMENTS}}} is {{{true}}}, the resulting modifier factors will be the average of each element; for example, if the enemy takes 0.5x damage from Blue and 1x damage from Red, an attack with the property {{{["red","blue"]}}} will inflict 0.75x damage. (Note that we have to multiply the variables by 1.0 in the averaging calculation to ensure the result comes out as a decimal number rather than an integer.)
{{{
<<else>>
<<for _v range $action.element>>
<<set _factor = Math.max(_target.getElement(_v,"percent"),_factor)>>
<<set _flatFactor = Math.min(_target.getElement(_v,"flat"),_flatFactor)>>
<</for>>
}}}
If {{{AVERAGE_ELEMENTS}}} is not {{{true}}}, we simply pick the best modifier (from the attacker's perspective). For example, if the enemy takes 0.5x damage from Blue and 1x damage from Red, an attack with the property {{{["red","blue"]}}} will inflict 1x damage because that is the higher modifier, ignoring the resistance to Blue completely. (This is how RPG Maker handles multi-element attacks.)
{{{
<<if _baseDmg < $dmg>>
<<set _elntmsg = setup.elementMessages.weakpoint>>
<<elseif _baseDmg > $dmg && $dmg > 0>>
<<set _elntmsg = setup.elementMessages.resist>>
<<elseif $dmg < _baseDmg && $dmg == 0>>
<<set _elntmsg = setup.elementMessages.immune>>
<<elseif $dmg < _baseDmg && $dmg < 0>>
<<set _elntmsg = setup.elementMessages.absorb>>
<</if>>
}}}
After we're done with the calculation, there is then a check for if the elemental attack hit a weakness or a resistance. If it's a weakness ({{{$dmg}}} is greater than the original damage), we store a message informing the player they hit a weak point; if it's a resistance ({{{$dmg}}} is less than the original damage), we store a message informing the player they hit a resistance; and so on. These messages are defined in {{{StoryInit}}}; by default, they use the messages from <i>Pokemon</i>. This is a means of transparency to the player, to let them know when they've hit a weakness or resistance. You can customize these messages or even remove them entirely if you see fit; you could even expand the code to create a unique message for every element, if you want!
{{{
<<if $B.subject.berserker is true>>
<<set $dmg *= (1+setup.BERSERK_FACTOR)>>
<</if>>
<<if $B.target.berserker is true>>
<<set $dmg *= (1+setup.BERSERK_FACTOR)>>
<</if>>
<<if $B.subject.defender is true>>
<<set $dmg *= setup.DEFEND_FACTOR>>
<</if>>
<<if $B.target.defender is true>>
<<set $dmg *= setup.DEFEND_FACTOR>>
<</if>>
<<if $B.target.shield is true>>
<<set $dmg *= (1-setup.SHIELD_FACTOR)>>
<</if>>
}}}
Then there's a call to "damageCalc custom factors", additional factors for modifying the final damage value. In the default engine, that's these effects. (Note that there are two branches each for Berserker and Defender; that's because they affect both incoming and outgoing damage.)
{{{
<<if ($dmg < _baseDmg && $dmg <= 0)>>
/* If elemental factors pushed damage to 0 or below, we shouldn't bump it up to the minimum; do nothing */
<<elseif _noDmgFloor && $dmg < 0>>
<<set $dmg = 0>>
<<set _noDmgFloor = false>> /* preventing data bleed */
<<elseif $dmg < setup.MIN_DMG>>
<<set $dmg = setup.MIN_DMG>>
<</if>>
}}}
Finally, the widget checks if the final damage value is lower than the minimum damage value. This should probably be a positive value; we don't want to accidentally heal people with our attacks by dealing negative damage, after all. (Unless they absorb the element, of course, so this check is bypassed if that's the case.) Some games will allow 0 damage to occur, but others have minimum damage values. For <i>Cartoon Battle</i>, I chose to be nice and have attacks always deal at least 1 damage, even if that may as well be nothing with the HP values I chose.
There is also a branch here that will set damage to 0 instead of the usual minimum damage through the flag {{{_noDmgFloor}}}. By default, this is used by the "Invincible" status effect, which renders someone immune to damage.
{{{
<<set $dmg = Math.round($dmg)>>
}}}
Finally, we must use the {{{Math.round()}}} function to remove any decimal values.
<h3 id="damage.2">Applying damage</h3>
{{{<<echoDamage>>}}} applies previously-calculated damage and alerts the player to this fact with a message.
{{{
<<if $args[0] instanceof Actor>>
<<set $B.target = $args[0]>>
<</if>>
}}}
The beginning allows you to apply these effects to a specific target other than the {{{$B.target}}} variable, by passing the character as the first argument to the widget. (Because Puppet and Enemy are subclasses of Actor, they will both fulfill the {{{instanceof Actor}}} evaluation.)
{{{
<<if !($args.includes("indirect") || target().dead) && subject() instanceof Puppet && target() instanceof Enemy && target().protectedBy !== null>>
<<set _temp = $B.target.name>>
<<set $B.target = $enemies.find(function(t) { return t && t.id === $B.target.protectedBy; })>>
<<print $B.target.name+" took the hit for "+_temp+"!\n">>
<</if>>
}}}
Then there's a check for protected characters. If the target is protected by someone, we need to change the target to their protector, and also print a message informing the player of this fact.
{{{
<<if def $bestiary && target() instanceof Enemy && $action.element>>
<<set $bestiary.fetch(target().name).statsKnown[$action.element] = true>>
<</if>>
}}}
This clause reveals the enemy's elemental affinity to the player if they are struck with an elemental attack. (For more information, see the Bestiary section in [[Additional Features]].)
{{{
<<if !($args.includes("nocalc") || $args.includes("indirect"))>>
<<damageCalc>>
<</if>>
}}}
For simplicity, {{{<<damageCalc>>}}} is run again here, but you may notice there's a clause to prevent this call if a "nocalc" argument is passed in. This allows functionality for special damaging attacks that don't use the normal damage formula, such as mass attack items. Indirect damage also bypasses this, as indirect damage has no subject and therefore cannot use the standard damage formula.
{{{
<<if !target().dead>>
}}}
Then we do a common-sense check to make sure the target isn't already defeated. There's no point in beating a dead goblin.
{{{
<<if target().shielded && !$args.includes("unblockable")>>
<<set _shield = target().effects.find(function (eff) { return eff && eff.shield; })>>
<<if ndef _shield>>
<<run console.log("ERROR in echoDamage: target is shielded but has no shield effect")>>
<<elseif _shield.onHit instanceof Function>>
<<run _shield.uses -= 1>>
<<print _shield.onHit(target())>>
<<else>>
<<run console.log("ERROR in echoDamage: shield effect has no onHit function")>>
<</if>>
}}}
Certain status effects also interact with direct damage, denoted by providing the "shielded" flag. If the target has the flag, we use the {{{find}}} function to obtain the shielding effect from the character's effects. (Note that {{{find}}} terminates at the first match regardless of how many matches actually exist in the array, so you will need more complex code if you want a strict hierarchy.) We then do a common-sense check to make sure we actually got something; it's not impossible that a coding oversight could make a {{{shielded}}} flag persist even after the shield effect is removed. If we did, we reduce the shield's {{{uses}}} (the number of attacks it can block) by 1 and call the shield's {{{onHit}}} function.
{{{
<<if def _elntmsg>><b><<print _elntmsg>></b><</if>>
<<if $dmg < 0>>
$B.target feeds on the energy, and recovers <<print $dmg*-1>> HP.
<<else>>
$B.target.name takes $dmg damage!
<</if>>
}}}
If {{{_elntmsg}}} exists (which is to say, the element mattered), it's displayed before the damage message. You can move it elsewhere if you prefer. I also chose to make a totally separate message for absorbing an attack, because <i>[Name] takes -10 damage!</i> would probably look very confusing to a player even if it does give you the information you need.
{{{
<<set $B.target.hp -= $dmg>>
(...)
<<deathcheck>>
}}}
And now we apply damage. Since absorbed damage is negative, {{{$B.target.hp -= $dmg}}} will work for all cases and doesn't require multiple clauses. After, we run {{{<<deathcheck>>}}} to apply the "dead" flag if the character has fallen below 0 HP.
{{{
<<if target().offbalance && $dmg >= 0>>
<<addEffect $B.target "Knocked Down" -1>>
<</if>>
}}}
But wait, there's more! After taking damage, the character is checked for the "Off-Balance" effect. If they have it, they gain a new "Knocked Down" effect. (As discussed in <a class="noExternal" href="#effects1">Applying Effects</a>, this will also remove the Off-Balance effect, but this functionality is built in to {{{<<addEffect>>}}}.) I chose to disallow this if the attack was absorbed, since logically you shouldn't be hurt by getting healed, but you could remove the clause if you think that's more logical.
Note that this check will not run for indirect damage, nor if the target has died from the attack.
<h4 id="dmgreflection">Damage Reflection</h4>
Some RPGs feature "damage reflection", which inflicts damage on an attacking character equal to the damage they dealt (or some proportion thereof). That's also handled through this widget.
{{{
<<if target().dmgreflection && !_dmgreflecting && target() !== subject() && $dmg > 0>>
<<set $dmg = Math.round($dmg * target().dmgreflection)>>
<<set _OG = {target: $B.target, subject: $B.subject}>>
<<set $B.target = _OG.subject; $B.subject = _OG.target>>
<<set _dmgreflecting = true>>
<<echoDamage "nocalc">>
<<set $B.target = _OG.target; $B.subject = _OG.subject>>
<<unset _OG; _dmgreflecting>>
<</if>>
}}}
Immediately after damage is applied to the target, we check if the target has a defined {{{dmgreflection}}} property. If it does, we calculate a new {{{$dmg}}} proportional to the target's {{{dmgreflection}}} value. (So, for instance, a {{{dmgreflection}}} of 1 would reflect all of the original damage, a {{{dmgreflection}}} of 0.5 would reflect half, etc.) We make sure to deactivate this clause if the subject is attacking itself, because being hit with your own damage reflection does not make logical sense and is liable to generate an infinite loop.
{{{
<<set _OG = {target: $B.target, subject: $B.subject}>>
<<set $B.target = _OG.subject; $B.subject = _OG.target>>
<<set _dmgreflecting = true>>
<<echoDamage "nocalc">>
<<set $B.target = _OG.target; $B.subject = _OG.subject>>
}}}
We then have to apply the damage to the attacker, the previous subject -- but wait! There are other effects that could occur after this action that are supposed to apply to the original target and subject. That means we have to store them in a temporary holder variable and set them back to the originals when we're done. We also set the {{{_dmgreflection}}} flag, which prevents further damage reflection within this action. This prevents an infinite loop from occurring if a damage reflector attacks another damage reflector.
We then apply the damage with a recursive call to {{{<<echoDamage>>}}}. You could bypass all of this by just applying the damage to the {{{subject}}} directly here, but the default engine chooses to run {{{<<echoDamage>>}}} for thoroughness, in case complications like the Bubble and Off-Balance effects are in play. (Note that reflected damage IS considered direct damage, for precisely this purpose.)
<b>NOTE:</b> {{{<<deathcheck>>}}} is only run <i>after</i> this section, which means a damage reflector will still reflect damage even if they're defeated by the attack. You can move it if you want different behavior.
<h4 id="dmg.onhit">onHit</h4>
{{{
<<if target().onHit instanceof Array && target().onHit.length > 0 && !_dmgreflecting && target().id !== subject().id>>
/* If target has onHit functions, they are executed here. Note that the target and subject are not reversed here, so if you want the effect to target the attacker, use "subject" as the selector, and vice versa. */
<<for _action range target().onHit>>
<<if _action instanceof Function>>
<<print _action()>>
<<else>>
<<run console.log("ERROR in onHit: onHit elements must be functions")>>
<</if>>
<</for>>
<</if>>
}}}
After attack reflection, we check for {{{onHit}}} effects. {{{onHit}}} effects are functions added to a character's {{{onHit}}} property, and they will execute here. These can be things like a toxic slime monster poisoning anyone who touches it, or Experiment 01 compelling hatred on anyone who attacks. These functions cannot be triggered by damage reflection or by a character attacking itself.
<i>(Note that {{{onHit}}} functions are distinct from counterattacks. Counterattacks are full actions that are executed after the initial action is completely resolved. These functions are resolved within the current action as part of damage application.)</i>
<h4 id="dmg.shock">Shock cures</h4>
After {{{<<deathcheck>>}}} but before the knockdown check, we check for shock cures. "Shock", in this case, refers to direct damage. In some RPGs, certain status effects like sleeping or confusion can be cured by direct damage, and this functionality is applied here.
{{{
<<if !$action.noShock>>
<<for _effect range target().effects.filter(function (eff) { return eff && Number.isInteger(eff.shock)})>>
<<set _shock = random(1,100)>>
<<if _shock <= _effect.shock>>
<<print target().removeEffect(_effect)>>
<</if>>
<</for>>
<</if>>
}}}
First we check to make sure the action does not have a {{{noShock}}} flag. This flag will prevent the action from triggering shock cures if it is {{{true}}}. (As an example, most RPGs only wake sleeping characters when they are struck by physical attacks, not by magic.) If not, we run a loop over the target's effects that have a {{{shock}}} property that is an integer value. (Any noninteger values for {{{shock}}} will be converted through a getter function into a Boolean {{{false}}} and fail this check.) We then roll a percentile die and check if we rolled under the effect's {{{shock}}} value. If yes, the effect is cured, and we run {{{removeEffect}}}, making sure to {{{<<print>>}}} the resulting removal message so the player knows what happened. (This means that a {{{shock}}} value of 100 will always result in a cure from direct damage. A Boolean value of {{{true}}} assigned to the effect's {{{shock}}} property will be converted to this value.)
Because we use a loop for this check, all viable effects will be checked, meaning it is possible for multiple effects to be removed by the same attack.
<h4 id="dmg.counters">Setting up counterattacks</h4>
This widget also sets up counterattacks.
{{{
<<if !(_dmgMods.includes("indirect") || _dmgMods.includes("nocounter") || _counterActive || target().dead || target().noact || target().retaliations === 0 || target() === subject())
&& _counters instanceof Array
&& !_counters.includes(target().id)
&& target().counter instanceof Action>>
<<if target().counter.trigger>>
<<run _counters.push(target().id)>>
<<run $B.actionQueue.push([target().id,target().counter])>>
<<if target().retaliations > 0>>
<<set target().retaliations -= 1>>
<</if>>
<</if>>
<</if>>
}}}
The first conditional does a lot of checks to see if we can use counters at all. To prevent infinite "countering counters" loops, we disallow this if there is already a {{{_counterActive}}}. Then, unlike with damage reflection, we will prevent the enemy from countering if they're dead or under a hold effect. The target must also have a nonzero number of retaliations, and the target must not be attacking itself. We will also disallow counters to indirect damage. With those checks passed, the {{{_counters}}} variable must exist and be an array, the target must not already be held in said array, and the target must have an Action stored in its {{{counter}}} attribute. Whew! That's a lot, but it's mostly just to confirm we don't have any {{{undefined}}} variables before proceeding.
{{{
<<run _counters.push(target().id)>>
<<run $B.actionQueue.push([target().id,target().counter])>>
<<if target().retaliations > 0>>
<<set target().retaliations -= 1>>
<</if>>
}}}
If all is well, we add the target's ID to the {{{_counters}}} array, and reduce their {{{retaliations}}} by 1. (Only if it's above 0 to start with, though -- if you set it to a negative number, the enemy can make unlimited counterattacks.) Note that the counterattack doesn't happen here -- we're only storing data that will be read later in <a class="noExternal" href="#actionqueue">the action queue</a>.
{{{
<<if target().counter.trigger>>
}}}
We also have this check. Counterattacks can also have a {{{trigger}}} attribute that determines when they will trigger. In <i>Pokemon</i>, for instance, there are some counters that only trigger on attacks that make contact; ranged attacks are safe. Or perhaps you want a counter to trigger <i>only</i> on ranged attacks? Only if the character is struck by an elemental weakness or resistance? There are lots of possibilities. You can define a custom {{{trigger}}} function in your counter action and have it return {{{true}}} only when the counter should be activated. By default, an undefined {{{trigger}}} always returns {{{true}}}, so this clause will always execute.
<h2 id="effects">Status Effects</h2>
Status effects are a common feature of RPGs, and give them greater tactical depth. They do things like temporarily changing your stats, or affecting what actions you can use.
In Another RPG Engine, status effects for each character are stored in an array, defined in the story JavaScript as an attribute of the <b>Actor</b> class. Most aspects of status effects are defined in the effect database, but the logic for applying an effect is handled through a special widget in the "Effect Adder" file.
<h3 id="effects1">Applying effects</h3>
For some effects, applying them to a character is just a matter adding the effect to the character's {{{effects}}} array, using an {{{Array.push()}}} command. But what if we want some rules for how effects are applied? Do we want players to be able to apply multiple copies of the same effect, or can only one instance be active at a time? Perhaps we want that rule applied to some effects, but not all? The effect adder handles this logic.
We're going to start with a few preliminary things:
{{{
<<set _E = new Effect($args[1])>>
}}}
We want to make an Effect object using just the effect's name. This is because the object's getter functions take care of some things for us based on the name, as can be seen in database-effects.js:
{{{
'Injury': {
"stackable": true,
"statmod": true
}}}
Attributes such as {{{stackable}}} will be useful to divide effects by desired behavior.
{{{
<<if !_target.dead || _E.persistAfterDeath>>
}}}
Then a common sense check: no point in applying effects to dead people, unless you want that. Some games, such as <i>Final Fantasy XII</i>, do have some status effects persist past unconsciousness and revival, and this feature is enabled through the {{{persistAfterDeath}}} attribute.
{{{
<<set _found = false>>
<<set _continue = true>>
<<set _msg = false>>
}}}
Our last preliminary action is to set these temporary variables. We will reference them later.
<h4 id="effects1.1">Accounting for protective effects</h4>
{{{
<<if !_E.unblockable>>
(...)
<<if _target.stasis is true>>
<<set _continue to false>>
<</if>>
<<if _continue is true and _target.chi is true and _target != _subject>>
/* self-inflicted statuses ignore Chi Shield */
<<if _E.buff>>
/* If applied effect is a buff, all is well, skip ahead */
<<set _continue to true>>
<<else>>
_target.name's Chi Shield protected _target.them from the ailment.
<<set _continue to false>>
<</if>>
<</if>>
}}}
First, we need to check if we can apply the effect at all. In the default engine, there is an effect called <b>Chi Shield</b> that protects the subject from negative ailments, and an effect called <b>Stasis</b> that prevents any changes to effects at all. For this effect to work as intended, we need to check if the target has this effect active whenever an effect is applied.
This is accomplished through the {{{if _target.stasis is true}}} and {{{if _target.chi is true}}} clauses. (You could also accomplish this by running a for loop over the target's effects array and checking for the name "Stasis", but this is faster. As explained in the next section, we will set a separate Boolean variable in the character when applying an effect to make checking for an effect easier.)
The {{{and _target != _subject}}} clause prevents Chi Shield from protecting the target from self-inflicted ailments, such as the "Winded" effect from Fighter's "Assault" ability. If you <i>do</i> want to protect characters from self-inflicted ailments, you can remove this clause.
{{{
<<if _E.buff>>
<<set _continue to true>>
<<else>>
_target.name's Chi Shield protected _target.them from the ailment.
<<set _continue to false>>
<</if>>
}}}
Now, we only want Chi Shield to block negative effects, not positive ones. This is the purpose of this section. If the effect is a buff, we set the {{{_continue}}} variable to true and let the effect be applied as normal. Otherwise, {{{_continue}}} is set to false, bypassing the next section, and a message is displayed informing the player that the target was protected. If you want a negative ailment to get through Chi Shield, you can give it the "unblockable" attribute, which will bypass protective effects. (If you want an ailment to bypass Chi Shield but <i>not</i> Stasis, you'll need to modify the code a little, possibly by making another attribute.)
The "Alert" status is similar, but it only protects against "Stunned".
{{{
<<set _name = _E.name>>
<<include "effect adder synonyms">>
<<if _continue && _target.tolerances.get(_name).current != 0>>
<<if _target.getTol(_name) == -1>>
_target.name is immune to _E.name.
<<set _continue to false>>
<<else>>
<<if _target.getTol(_name) > 0>>
_target.name's tolerance to _E.name was weakened.
<<set _continue to false>>
<<run _target.decTol(_name)>>
<<else>>
<<set _continue to true>>
<<run _target.resetTol(_name)>>
<</if>>
<</if>>
<</if>>
}}}
If the effect makes it through protective effects, it is then checked against the target's tolerances. {{{if _target.tolerances.get(_name).current != 0}}} checks if the target has a tolerance for the status effect at all; if they don't, we can ignore this entire check. (Recall from <a class="noExternal" href="#JS.tolerances">the Tolerance class</a> that a negative {{{current}}} value in a Tolerance conveys immunity, while a positive value conveys resistance; a value of 0 means the target has no tolerance.) Otherwise, we check what the current tolerance value is. If it's -1, the target has an immunity, we print a message informing the player, and we set {{{_continue}}} to false, ending the widget. Otherwise, we go on to check if it's above 0. If it is, {{{_continue}}} is set to {{{false}}}, ending the widget, but the target's tolerance is decreased by 1 through the {{{decTol}}} function. If a tolerance has been exhausted, the program skips to the {{{<<else>>}}} clause, resets the tolerance, and applies the effect as normal.
The preceding {{{effect adder synonyms}}} passage is for when we want certain effects to have entangled tolerances. In the default engine, one example is the "Forsaken" effect: Because it is a derivative of the "Curse" ability, I chose to have the same tolerance apply to both. For this to work, we need to alter the {{{_name}}} variable used to check against the target's tolerances: if the effect is Forsaken, we check against Curse tolerance rather than Forsaken tolerance. In all other cases, the {{{_name}}} value is the same as the effect's name, and everything proceeds as expected.
<h4 id="effects1.2">Numerical components</h4>
Some effects only operate in binary, but some have variable, numerical components. For instance, take a stat-modifying effect. How do we calculate exactly how much the stat will be changed? You could implement it as a flat rate, such as a 50% boost or penalty. Many RPGs, such as the <i>Final Fantasy</i> series, do this. But what if we wanted to do something more complex, where the numerical component depends on something like the caster's Special stat? In that case, we'll need to calculate it every time the effect is applied.
{{{
<<if _continue is true>>
/* First, calculate effect power. */
/* debuff effects are calculated on first application (do not change if target SPC changes) */
<<if $args[3] instanceof Number>>
<<set $power = $args[3]>>
<<else>>
<<if _E.statmod and _E.name isnot "Knocked Down">>
<<if _E.buff is false>>
<<effectcalc "debuff">>
<<else>>
<<effectcalc "buff">>
<</if>>
/* DoT damage is calculated every time it activates, so only attacker's side is applied to effect power here. */
<<elseif _E.dot>>
<<set $power = [_subject.get("Special"),$action.effweight]>>
/* Everything else has no numerical component, but we'll set power to a number to prevent possible glitches. */
<<else>>
<<set $power = 0>>
<</if>>
<</if>>
}}}
This is what the next section does. Different effects have their numerical components calculated in different ways, so we need to use an {{{<<if>>}}} tree to filter effects. Buffs and debuffs use slightly different formulas, which you can find in the {{{<<effectcalc>>}}} widget. Damage-over-time effects have a more complex calculation based on both the subject's Special stat and the effect weight of the ability, so those are both stored in the {{{$power}}} variable as an array.
If for some reason we want to bypass all of this and just set the effect's power as a flat number, we can also do that. As seen in the first {{{<<if>>}}} clause, passing a number as the fourth argument will just set that to the {{{$power}}} variable.
<h4 id="effects1.3">Stackability</h4>
You may want some effects to be "stackable", which means multiple copies of the same effect can exist on a character.
{{{
<<if _E.stackable>>
<<run _target.effects.push(new Effect(_E.name,$args[2],$power))>>
}}}
We check for this with another {{{<<if>>}}} statement. If the effect is stackable, we can simply add the effect with no further complications. If not, we enter special cases.
{{{
<<elseif _E.exclusive>>
<<for _j, _effect range _target.effects>>
/* remove all stance effects */
<<if _effect.exclusive>>
<<run _target.removeEffect(_effect,{pierce: true})>>
<</if>>
<</for>>
}}}
Then there is a special branch for mutually exclusive effects. Only one of the effects listed here can be active at the same time. For this, we must use a for loop to search over the character's effect array. If any effects have a mutually-exclusive name, they are removed. After the check is performed, the mutually exclusive effect is added.
{{{
<<if _E.name is "Martyr">>
<<if _target instanceof Puppet>>
<<set _party = $puppets>>
<<elseif _target instanceof Enemy>>
<<set _party = $enemies>>
<</if>>
<<find "_party" "martyr" "true">>
<<if _pos > -1>>
<<set _act = _party[_pos]>>
<<find "_act.effects" "name" "\'Martyr\'">>
<<run _act.removeEffect(_act.effects[_pos],{pierce: true})>>
<</if>>
<</if>>
}}}
Additionally, there can only be one "Martyr" effect active at a time, so if that's the effect being applied, we have to check if anyone else has it and if they do, remove it. We find the party we need to search by checking whether the current subject is a Puppet or an Enemy object, then use {{{<<find>>}}} to see if there is anyone with the {{{martyr}}} attribute flagged as {{{true}}}. If there is, we'll have to do another {{{<<find>>}}} to locate the Martyr effect in their {{{effects}}} array and remove it.
{{{
<<else>>
<<for _k, _effect range _target.effects>>
<<if _effect.name is _E.name>>
/* if effect is already applied, increase its duration */
<<if $args[2] > _effect.duration>>
<<set _effect.duration = $args[2]>>
<</if>>
}}}
Every other effect behaves the same way for the purposes of application, so they can all be handled under one "else" case. We first perform a search over the target's effect array like we did for the mutually exclusive effects. If we find the effect to be applied is already there, we don't add it to the array, but we do renew its duration (provided the applied effect's duration is longer).
p
{{{
<<if _E.dot>>
<<set _E = new DoT(_E.name,$args[2],$power[0],$power[1])>>
<<if _effect.damage(_target) < _E.damage(_target)>>
<<set _effect.power to $power[0]>>
<<set _effect.weight to $power[1]>>
<</if>>
<<else>>
<<if $power > _effect.power>>
/* if the new application has higher power than the original effect, overwrite original power */
<<run _effect.onRemove(_target); _effect.power = $power; _effect.onApply(_target)>>
<</if>>
<</if>>
}}}
For effects with numerical components, we also replace the effect's power if it's greater. This is simple enough for most effects, but damage-over-time effects have a more complicated form of this, as their numerical effects rely on multiple factors. To find out if the new effect is stronger, we need to create a new {{{DoT}}} object using the arguments passed to the widget, and compare its {{{damage()}}} function to the extant effect. If the extant effect would do less damage, its power is replaced.
For other effects, we will need to run {{{onRemove()}}} and {{{onApply()}}} in-between adjusting the power, because we need to clear the effects of the old value before recalculating.
{{{
<<set _found to true>>
<<break>>
}}}
Now, all that was taking place inside the loop! Since the effect wasn't stackable, there shouldn't be any other instances of the same effect, so we don't need to run through the rest of the loop. We'll set a variable showing we found the effect, then we exit with a {{{<<break>>}}} command to save processing power.
{{{
<<if _found is false>>
/* if the effect isn't there already, add it */
<<if _E.dot>>
<<run _target.effects.push(new DoT(_E.name,$args[2],$power[0],$power[1]))>>
<<elseif _E.name is "Off-Balance">>
/* if already knocked down, can't be off-balance */
<<if _target.down is false>>
<<run _target.effects.push(new Effect(_E.name,$args[2],$power))>>
<<else>>
_target.name would have been pushed off-balance, but they're already knocked down!
<</if>>
<<elseif _E.name is "Knocked Down">>
<<run _target.effects.push(new Effect(_E.name,$args[2],$power))>>
/* Being knocked down also removes off-balance: */
<<for _k, _effect range _target.effects>>
<<if _effect.name is "Off-Balance">>
<<run _target.removeEffect(_effect)>>
<</if>>
<</for>>
<<else>>
<<run _target.effects.push(new Effect(_E.name,$args[2],$power))>>
<<if _E.name is "Protector">>
<<set _p = _target.id>>
<<set $B.target.protectedBy = _p>>
<</if>>
<</if>>
<</if>>
}}}
Otherwise, if {{{_found}}} is false, we will add the effect as normal. Damage-over-time effects have an additional attribute, and therefore must be added under a special clause. "Off-Balance" and "Knocked Down" also have special interactions that are accounted for.
{{{
<<if _E.name is "Protector">>
<<set _p = _target.id>>
<<set $B.target.protectedBy = _p>>
<</if>>
}}}
There are additional special cases here as well. The "Protector" effect requires a special clause due to its special behavior that requires keeping track of the protected character. This clause sets the "protectedBy" attribute of the target, which is necessary for the effect to trigger during enemy targeting.
<h3 id="effects.calc">Calculating numerical effects</h3>
The "Damage and Formulas" file includes not just the formulas for regular attacks, but for status effects as well.
<h4 id="effects.calc.mods">Stat mods</h4>
{{{
<<widget "effectcalc">>
<<switch $args[0]>>
<<case "debuff">>
<<set $power = Math.round((setup.effbase+setup.effdamper*(_subject.get("Special")-_target.get("Special")))*$action.effweight)>>
<<if $power < setup.min_debuff>>
<<set $power = setup.min_debuff>>
<</if>>
<<case "buff">>
<<set $power = Math.round((setup.effbase+setup.effdamper*(_subject.get("Special")))*$action.effweight)>>
<<if $power < setup.min_buff>>
<<set $power = setup.min_buff>>
<</if>>
<</switch>>
<</widget>>
}}}
Stat mods are calculated using {{{<<effectcalc>>}}}, and their formula is very similar to that of regular attacks. Different base and damper values are used, which can be set in StoryInit.
After calculation, there is a check against the minimum value. By default, this is set to 5 for debuffs and 0 for buffs. It can be changed in StoryInit.
Discussion on the choice of formula and other possible options can be found in [[the design page|Design]].
<h4 id="effects.calc.dot">Damage-over-time effects</h4>
Damage-over-time calculation is handled through the effect database. Before the definitions for each effect, several functions are defined in the Effect class definition:
{{{
var physical = function (puppet) {
return this.weight*(setup.base + (setup.damper * (this.power - puppet.get("Defense"))));
}
var special = function (puppet) {
return this.weight*(setup.base + (setup.damper * (this.power - puppet.get("Special"))));
}
var piercing = function (puppet) {
return this.weight*(setup.base + (setup.damper * this.power));
}
var proportional = function (puppet) {
return this.weight * puppet.maxhp;
}
var fixed = function (puppet) {
return this.power;
}
}}}
In the default engine, there are five kinds of DoT behaviors: physical, special, piercing, proportional, and fixed. Physical damage, used by the "Burning" effect, is reduced by the target's Defense; special damage, used by the "Poisoned" effect, is reduced by the target's Special; piercing damage, used by the "Peridition" effect, ignores defenses; proportional damage inflicts damage based on a proportion of the target's max HP; and fixed damage is just whatever you set as the object's power value.
(Note that proportional damage <b>multiplies</b> HP by its weight. The weight of a proportional effect is equal to the proportion it takes off; a weight of 0.125 or (1/8) will take an eighth of the character's HP per turn.)
The actual application of damage is performed through the {{{damage()}}} function of the DoT class, which is defined later in the database file:
{{{
window.DoT = class DoT extends Effect {
constructor(name,time,power,weight){
super(name,time,power);
this.weight = weight;
this._dot = true;
this.damage = function (puppet) {
var dmg = Math.round(this.dmgtype(puppet));
if (dmg < setup.min_DoT){
dmg = setup.min_DoT;
}
return dmg;
}
}
}
}}}
This function is passed a target that is then used to calculate damage based on the formula type, followed by cleanup -- rounding to an integer, and checking against the minimum DoT damage. (This value is set to 1 by default, and can be changed in StoryInit.)
<h3 id="effects.loss">Loss-of-control Effects</h3>
Loss-of-control effects are status effects that allow the character to continue acting, but take control away from you, causing them to instead act under an AI routine. You may be familiar with these from the "Berserk" and "Confusion" effects in many RPGs.
In this engine, we handle these effects through a passage included in {{{<<specialcheck>>}}}, which is called constantly during battle:
{{{
<<set _u = $puppets.find(function (p) { return (p.uncontrollable && !(p.isDone || p.noact || p.dead)); })>>
<<if def _u>>
<<set $B.subject = _u>>
<<if subject().name == "Mage">>
<<set $action = new Action("Blast")>>
<<elseif subject().name == "Witch">>
<<set $action = new Action("Pox")>>
<<else>>
<<set $action = clone(subject().defaultAction)>>
<</if>>
<<if subject().en < $action.cost>>
<<set subject().isDone = true>>
<<goto "Battle!">> /* necessary to find other potential uncontrollables */
<<else>>
<<if _u.confusion>>
<<randomTarget "any">>
<<elseif _u.charmed>>
<<randomTarget $puppets>>
<<elseif _u.hatred>>
<<randomTarget $enemies>>
<</if>>
<<goto "action phase">>
<</if>>
<</if>>
}}}
This code finds the first puppet who has been marked "uncontrollable" (excluding, of course, puppets who are done, paralyzed, or dead). If no such puppet is found, {{{_u}}} will be undefined and the rest of this code will be ignored. If a puppet is found and stored in {{{_u}}}, we make it the active subject, set the active action to its default attack, and forward them to the action phase, forcing them to attack. (For most cases we can use the puppet's {{{defaultAction}}} for this, but Mage and Witch have nondamaging default actions, so we need special handlers for them.)
{{{
<<if subject().en < $action.cost>>
<<set subject().isDone = true>>
<<goto "Battle!">> /* necessary to find other potential uncontrollables */
}}}
Of course, we need to be mindful of action limitations. Basic attacks aren't free in the default system, so it's possible an uncontrollable character is unable to pay the cost. There are a lot of ways you can handle this eventuality, but for simplicity, I just end the character's turn. We then have to refresh the passage so we can run this check again, in case there's another uncontrollable puppet. (The {{{find}}} function only finds the <i>first</i> element that matches its criteria, not all of them.)
{{{
<<if _u.confusion>>
<<randomTarget "any">>
<<elseif _u.charmed>>
<<randomTarget $puppets>>
<<elseif _u.hatred>>
<<randomTarget $enemies>>
<</if>>
}}}
The puppet needs a target before they can attack, also. That's handled through the {{{<<randomTarget>>}}} widget (see <a class="noExternal" href="#targeting.basic">Standard Targeting</a>), with branches for specialized behavior depending on the exact effect the puppet is under. Confused puppets can target anyone, charmed puppets will only target their allies, and hateful puppets will only target their enemies. The effects will also take precedence in that order, a necessary feature if the puppet is under multiple loss-of-control effects simultaneously.
This is a pretty bare-bones functionality. For more discussion on these effects and what you can do with them, see [[Design]].
<h2 id="targeting">Targeting and Enemy AI</h2>
Enemies need to be able to select attacks and targets just like players. But unlike with player choices, there's no person to make selections, so we need a program to do it instead.
We could just have enemies choose actions and targets randomly, but that wouldn't make for a very engaging game; the difficulty of a battle will vary depending on whether enemies target a more vulnerable character, if they gang up on one character or if they spread damage out... etc. If that's all random, the game isn't very fair; a player might do the exact same thing and get a completely different result, just because, say, Mage never got targeted through the simple luck of the draw. So we want to make enemies a bit more intelligent, to make the player's experience more even. We also need a way to determine behavior for player characters under <a class="noExternal" href="#effects.loss">loss-of-control effects</a>.
The widgets used to a accomplish this are stored in their own file, "Targeting".
<h3 id="targeting.basic">Standard Targeting</h3>
Regular targeting is handled by the {{{<<randomTarget>>}}} widget.
{{{
<<if deadCount() == puppets().length || enemies().filter(function (e) { return e.dead; }).length == enemies().length>>
<<set _targetfail = true>>
}}}
The very first thing we have to do is check if all the characters in a party are already defeated. This can happen if the party is wiped in the middle of the turn or a multi-hit attack. Since dead characters can't be targeted, we'll run into an error if we try to run the widget in this state. So instead, we set a {{{_targetfail}}} flag and end the widget here.
{{{
<<set _modifiers = []>>
<<if $args[0] instanceof Array>>
<<set _modifiers = $args[0]>>
<<else>>
<<for _arg range $args>>
<<run _modifiers.push(_arg)>>
<</for>>
<</if>>
<<set _continue = true>>
}}}
Then we have to do some processing on the arguments passed to the widget. There are cases where we may need to recursively call this widget with the same arguments passed at the start. The easiest way to keep that consistent is to lump all the arguments into an array and pass that as the first argument to any recursive calls. This checks if that's already been done, otherwise it generates a {{{_modifiers}}} array from the arguments.
We also have to initialize the {{{_continue}}} variable for later use.
{{{
<<if subject() instanceof Puppet>>
<<if _modifiers.includes("any")>>
<<set _selector = random(1,2)>>
<<switch>>
<<case 1>>
<<set _party = $puppets>>
<<case 2>>
<<set _party = $enemies>>
<</switch>>
<<else>> /* else pass the target party as the first argument */
<<set _party = _modifiers[0]>>
<</if>>
<<else>>
<<set _party = $puppets>>
<</if>>
}}}
Next, remember that we use this widget for both enemies <i>and</i> puppets. If the subject is a puppet under a loss-of-control effect, their target party is variable. We determine the target party from the first argument passed to the widget, or we flip a coin if the argument is "any".
From there, the widget runs through the targeting criteria in highest to lowest priority, so our special cases come first.
<h4 id="targeting.basic.1">Martyr check</h4>
{{{
<<widget "martyrCheck">>
<<set _martyrTest = _party.find(function (p) { return p && p.martyr === true })>>
<<if _martyrTest>>
<<set $B.target = _martyrTest>>
<<set _continue to false>>
<</if>>
<</widget>>
}}}
In the default engine there's an effect, "Martyr", that forces all attacks to target the recipient. This ought to bypass any normal enemy behavior, so we should implement it first. All we have to do is use the {{{find}}} function to see if there are any battlers with the {{{martyr}}} flag. If there's not, {{{_martyrTest}}} will be {{{undefined}}}, and the next conditional will fail. Otherwise, the returned battler becomes the new target. If our target was found, we also want to set {{{_continue}}} to false to tell the program not to continue with the targeting logic.
(As the comment says, this will stop at the first position it finds even if there are multiple matches. As previously discussed in <a class="noExternal" href="#effects1">the effect adder</a>, there is a check to ensure only one Martyr can exist at a time.)
There is then a check against {{{_continue}}}. If a target was found, the program stops here. Otherwise, we continue.
<h4 id="targeting.basic.2">Reactive enemies</h4>
{{{
<<if $B.subject.aggro && def $attacker>>
<<set $B.target = $puppets[$attacker]>>
<<if !$B.target.dead>>
<<set _continue = false>>
<</if>>
<</if>>
}}}
The next priority is reactive enemies. As detailed in <a class="noExternal" href="#actionphase">the action phase</a>, a character's position is recorded in {{{$attacker}}} if the enemy's HP is lower at the end of the round than the start, i.e., if they were attacked by a damaging move. If you give an enemy the attribute "aggro", they'll bypass normal targeting and always target whoever attacked last.
We also need to include {{{&& def $attacker}}} in the if clause; otherwise, you'll get an error if no one attacked and {{{$attacker}}} wasn't set. There is also a check just in case the attacker dies in between attacking and the enemy's turn.
(Note that there's no check for untargetable protection, as stated in the comments. You can add another if statement to check for it if you like, but it'll get tricky if you want some enemies or attacks to ignore it and some not.)
If none of those conditions were triggered, {{{_continue}}} will remain {{{true}}} and the program continues to standard targeting.
<h4 id="targeting.basic.untargetable">Untargetability</h4>
...But before we do that, we have to do another check!
{{{
<<if !_modifiers.includes("ignore untargetable")>>
<<set _untargetTest = _party.filter(function (p) { return (p.dead || p.untargetable) })>>
<<if _untargetTest.length == _party.length>>
<<run _modifiers.push("ignore untargetable")>>
<</if>>
<</if>>
}}}
In the default engine, there's an effect, "Hidden", that renders characters "untargetable", making this widget ignore them. But wait: If only an untargetable character is left alive, does that mean they become invincible?
The answer, at least in the default engine, is "no", and this check is why. We use the {{{filter}}} function to extract only the characters that are dead or untargetable from the {{{_party}}} array. If this array is equal to the length of the full {{{_party}}} array, only untargetable puppets are still standing. We'll strip their protection before proceeding by adding the "ignore untargetable" argument to {{{_modifiers}}}.
After this is done, we initialize the {{{_hitlist}}} variable to an empty array, because we'll need it in the next section.
<h4 id="targeting.basic.3">Mercy</h4>
{{{
<<if $B.subject.mercy < 1 or _modifiers.includes("smart")>>
/* if enemy's mercy is below 1, they will always use smart targeting */
<<set _mercy = 2>>
<<else>>
/* 1 in (mercy) chance of random targeting, to give players a break */
<<set _mercy = random(1,$B.subject.mercy)>>
<</if>>
}}}
The first thing we do is set the {{{_mercy}}} variable. You might consider the game too hard if enemies always make the perfectly optimal choices. You can use the mercy mechanic to give players a bit of a break. If {{{_mercy}}} is 1, the enemy won't behave intelligently and will just target randomly. You can customize each enemy's intelligence level with their "mercy" attribute, defined in the story JavaScript. It's a little unintuitive: the chance of bypassing smart targeting will be the inverse of their "mercy" value, so a higher value will make an enemy more merciless. If you set "mercy" to 1, the enemy will always target randomly. Conversely, if you set "mercy" to 0, the first {{{<<if>>}}} statement will ensure they always use smart targeting. You can also accomplish this by passing the string "smart" to the {{{<<randomTarget>>}}} widget.
In the default engine, all enemies have their "mercy" values set to 3, so they have a 1-in-3 chance of targeting randomly. You can give specific enemies different values by manually setting their "mercy" values in the enemy database.
<h4 id="targeting.basic.4">Smart targeting</h4>
This behaves slightly differently depending on the difficulty setting, necessitating a conditional fork. Here we will cover the most complicated variant, which is hard mode.
{{{
<<set _hitlist = []>>
}}}
In all cases, the first thing we need to do is create the {{{_hitlist}}} variable as an empty array. This is going to be where we store viable targets.
{{{
<<if !_modifiers.includes("ignore downed")>>
<<for _i, _puppet range $puppets.filter(function (p) { return !p.dead && (p.offbalance || p.down); })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
}}}
First, the enemy is going to check if a character has the "Off-Balance" or "Knocked Down" effect. In the default engine, these effects make characters more vulnerable, so it's smart to focus attacks on these characters. We accomplish this by running a for loop over the {{{$puppets}}} array filtered for characters with the "offbalance" or "down" flags. (Here we can see why it's easier to mark these effects as attributes: we'd have to run another for loop over the character's effects to find these otherwise.) If it is, their address in the array is added to {{{_hitlist}}}.
We also wrap this in an {{{if _modifiers.includes("ignore untargetable") || !_puppet.untargetable}}} statement, since untargetable characters are immune to direct attacks -- unless, of course, the attack ignores untargetability.
Smart targeting will check for these effects by default, but in case you don't want that (such as for a nondamaging attack, perhaps), you can pass the string "ignore downed" to the widget, and the {{{<<if>>}}} statement at the beginning will bypass this section.
{{{
<<if _modifiers.includes("debuff")>>
<<for _i, _puppet range $puppets.filter(function (p) { return !(p.dead || p.chi || p.stasis) && p.get("Special") < p.getBase("Special"); })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
}}}
You can also make the enemies use debuffs intelligently. Debuffs are more effective if the target has lower Special, so enemies will preferentially target characters whose Special is lower than their base value (i.e., they have been afflicted by a Special-reducing debuff). Unlike downed targeting, this isn't active by default, and you activate it by passing the string "debuff" as an argument to the widget.
{{{
<<if _hitlist.length > 0>>
<<set _continue to false>>
<</if>>
}}}
We end by checking if the smart targeting found anything. We check this by asking if {{{_hitlist}}} has any length. If it contains nothing, that means nothing was added, which means no viable targets were found. If the clause does trigger, we set {{{_continue}}} to false to skip the next section.
<h4 id="targeting.basic.5">Normal targeting</h4>
{{{
<<if _continue>>
<<for _puppet range $puppets.filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
}}}
If smart targeting didn't find anything, this clause will activate and execute normal targeting. This is virtually identical to smart targeting, but we don't need to make any special checks; everyone is added to the hitlist automatically.
{{{
<<elseif $difficulty is "medium">>
(...)
}}}
On medium difficulty, this is blended with smart targeting: instead of enemies only picking from vulnerable targets, vulnerable targets are simply added to the array twice, making them more likely targets but still allowing for the possibility they'll be passed over.
(On easy difficulty, smart targeting doesn't occur at all.)
{{{
<<if def _hitlist && _hitlist.length > 0>>
<<set _origHitlist = clone(_hitlist)>>
<<for _t range _origHitlist.filter(function (p) { return p.firefly === true; })>>
<<run _hitlist.push(_t)>>
<</for>>
<<set _n = random(0,_hitlist.length-1)>>
<<set $B.target = _hitlist[_n]>>
}}}
We finish by picking a target out of {{{_hitlist}}} through a randomly-generated index value. If a character is added to the hitlist multiple times, they are more likely to be chosen. We can see this implemented through the "Firefly" ability here:
{{{
<<set _origHitlist = clone(_hitlist)>>
<<for _t range _origHitlist.filter(function (p) { return p.firefly === true; })>>
<<run _hitlist.push(_t)>>
<</for>>
}}}
If a character in the hitlist has the "Firefly" trait, they're added to the hitlist a second time. (Note that there's a catch-22 here: We can't modify an array while looping over it! That's why we need to clone it to a different variable first.)
As an example, in the default setup, Fighter's "firefly" flag means that normal targeting would produce a hitlist of [Rogue,Fighter,Fighter,Mage], resulting in a 50% chance of targeting Fighter and a 25% chance of targeting the others.
<h4 id="targeting.basic.7">Protection check</h4>
{{{
<<if $B.target !== null && $B.target.protectedBy !== null>>
<<set _temp = $B.target.name>>
<<set $B.target = $puppets.find(function(t) { return t && t.id === $B.target.protectedBy; })>>
<<set _targetingMsg = $B.target.name+" took the hit for "+_temp+"!\n">>
<</if>>
}}}
The very last thing is the protection check. We use an {{{<<if>>}}} statement to check if there's anything in the target's "protectedBy" attribute. We also have to check that {{{$B.target}}} isn't null, because it's possible (if the whole party is dead) that a target wasn't found. We then use the {{{find}}} method to determine which character's ID matches the ID stored in {{{protectedBy}}}, and change the target to them.
<h4 id="targeting.basic.puppets">Puppet Targeting</h4>
{{{
<<if subject() instanceof Puppet>>
<<set $B.target = null>>
<<for target() === null>>
<<set _rand = random(0,_party.length-1)>>
<<if !_party[_rand].dead && (_modifiers.includes("ignore untargetable") || !_party[_rand].untargetable)>>
<<set $B.target = _party[_rand]>>
<</if>>
<</for>>
}}}
Going back to the top, this code runs before normal targeting. If the subject is a puppet under a loss-of-control effect, they just select a target completely randomly, no complex logic involved. This is mainly because puppets do not have {{{mercy}}} or {{{threat}}} attributes, and including a handler for them at every step would have been a hassle. You can expand this functionality if you wish.
<h3 id="targeting.threat">Threat-based/Aggro Targeting</h3>
There's an alternative to normal targeting you may like to use, especially if you're using a system like <i>Dungeons & Dragons</i> where range and positioning is important.
<b>Threat-based targeting</b>, enabled by activating the {{{THREAT_TARGETING}}} variable in {{{StoryInit}}}, uses a statistic, called "threat" here, to determine which character enemies attack. There are a lot of ways this can be done, and it may be instructive to look into aggro systems in other games if you'd like to modify the default system.
<h4 id="targeting.threat.1">Setup</h4>
{{{
if (setup.THREAT_TARGETING === true) {
this.threat = new Map();
V().puppets.forEach(function(puppet) {
this.threat.set(puppet.name,puppet.initialThreat());
}, this);
}
}}}
The very first thing we have to do is to initialize the threat statistic so we have one at all. That's taken care of through this code in <a class="noExternal" href="#JS.enemies">the Enemy class constructor</a>. If we've enabled threat targeting, we create a {{{Map}}} with a numerical threat value keyed to every puppet on the field, and assign it as a property of the Enemy object.
{{{
initialThreat(){
return 1;
}
}}}
<code>initialThreat</code> is defined in the Puppet class. In theory, you may want to implement some more code here that modifies the initial threat based on some other variables, but by default it just returns a value of 1.
{{{
<<if setup.THREAT_TARGETING === true && subject() instanceof Puppet && target() instanceof Enemy>>
<<include "echoDamage threat gain">>
<</if>>
(...)
<<set _threatGain = 100 * ($dmg / target().maxhp)>>
<<if subject().firefly>>
<<set _threatGain *= 2>>>
<</if>>
<<run target().threat.inc(subject().name,_threatGain)>>
}}}
Additional threat is generated when player characters inflict damage in {{{<<echoDamage>>}}}. Threat gain is equal to the percentage of maximum HP inflicted by the attack. You can also add special cases for various circumstances -- perhaps certain attacks draw more or less threat, or, as here, special traits like Firefly affect it -- or make a completely different formula. The threat formula is offloaded to its own passage, "echoDamage threat gain", to make this easier.
{{{
<<if setup.THREAT_TARGETING === true && target() instanceof Enemy && subject() instanceof Puppet>>
<<run target().threat.inc(subject().name,$action.threat)>>
<</if>>
}}}
You can also make actions provide a flat threat increase. In <i>Dragon Age</i>, this typically included ranged attacks and spells, which makes sense, as those attacks are used by dangerous yet vulnerable targets that smart opponents should prioritize. You can also use this to make non-damaging attacks draw threat.
{{{
decayThreat () {
if (setup.THREAT_TARGETING === true) {
this.threat.forEach(function(value,key) {
this.threat.set(key,value-setup.THREAT_DECAY);
if (this.threat.get(key) < 1) {
this.threat.set(key,1);
}
}, this);
}
}
}}}
Finally, we want a way for threat to change over time -- if a character stops attacking, logicially they should draw less attention. This method function, defined in the Enemy class and called at the end of the enemy turn, reduces threat by a small amount specified in {{{StoryInit}}} (10 by default).
<h4 id="targeting.threat.2">Logic</h4>
Now that we've set up our variables, we can build the actual targeting logic. This is handled by the widget {{{<<threatTarget>>}}}, which is automatically called by {{{<<randomTarget>>}}} if threat targeting is enabled. (Calling it as part of {{{<<randomTarget>>}}} helps for compartmentalization -- martyr, aggro, and protection checks should still apply to threat targeting, so we can keep those running as normal and just change the targeting logic.)
{{{
<<if _modifiers.includes("random")>>
<<set $B.target = null>>
<<for target() === null>>
<<set _rand = random(0,$puppets.length-1)>>
<<if !$puppets[_rand].dead && (_modifiers.includes("ignore untargetable") || !$puppets[_rand].untargetable)>>
<<set $B.target = $puppets[_rand]>>
<</if>>
<</for>>
<<set _continue = false>>
<</if>>
}}}
First we have a handler in case we want to, for some reason, bypass the threat system we just set up. You can do this by passing a "random" argument to the targeting widget. This will select a target purely randomly. We have to do a quick check to make sure we don't select a dead or untargetable target, and run a loop to reroll the selector until we find a viable one. After this we set {{{_continue}}} to {{{false}}} to show that we've found our target and the rest of the code doesn't need to execute.
Otherwise...
{{{
<<set _totalThreat = 0>>
<<for _puppet range $puppets.filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<set _threat = subject().threat.get(_puppet.name)>>
<<run _hitlist.push({target: _puppet, threat: _threat})>>
<<set _totalThreat += _threat>>
<</if>>
<</for>>
}}}
The algorithm for this targeting logic is that characters have a {{{(personal threat) / (total threat)}}} chance of being targeted. We're going to have to juggle some numbers to make this work correctly. We need to calculate the total threat, for starters. We initialize a {{{_totalThreat}}} variable to 0, then run a loop over {{{$puppets}}} (excluding defeated characters) to add all their threat values together. In the process, we'll also add them to the {{{_hitlist}}}, as both the character themselves and their corresponding threat value.
{{{
<<if _hitlist.length > 0>>
<<set _rand = random(1,_totalThreat)>>
<<for _i, _target range _hitlist>>
<<if _i != 0>>
<<set _target.threat += _hitlist[_i-1].threat>>
<<if _rand <= _target.threat && _rand > _hitlist[_i-1].threat>>
<<set $B.target = _target.target>>
<</if>>
<<elseif _rand <= _target.threat>>
<<set $B.target = _target.target>>
<</if>>
<</for>>
<<else>> /* everyone's dead, flag targetfail */
<<set _targetfail = true>>
<</if>>
}}}
With the {{{_hitlist}}} constructed, we're ready to select a target. We have to make a common-sense check that we have a populated hitlist at all; otherwise, there are no viable targets and we need to flag {{{_targetfail}}}. We then select a random number between 1 and {{{_totalThreat}}}, and see which character that value lands on.
{{{
<<if _i != 0>>
<<set _target.threat += _hitlist[_i-1].threat>>
<<if _rand <= _target.threat && _rand > _hitlist[_i-1].threat>>
<<set $B.target = _target.target>>
<</if>>
}}}
To accomplish this, we'll need to increment the {{{threat}}} properties in the hitlist as we go along, so that every character has a unique range. We'll do this by adding the previous character's {{{threat}}} to their own. Then, if the selector is less than or equal to the character's threat value and greater than the proceeding character's threat value, they're the target.
{{{
<<elseif _rand <= _target.threat>>
<<set $B.target = _target.target>>
<</if>>
}}}
Of course, since we're referencing the previous element of an array, we need a handler for when there is no previous element. If {{{_i}}} is 0, we're examining the first element of the array, and all we need to do is check if {{{_rand}}} is less than or equal to that character's threat.
As an example for how this would work, let's say we have three characters with threat values 1, 50, and 100, respectively, none of whom are untargetable. {{{_totalThreat}}} would be {{{1 + 50 + 100 = 151}}}, and so {{{_rand}}} could be any value from 1 to 151. Let's say it's 101. That's not less than or equal to 1, so Character #1 isn't selected. We move on to Character #2, who gains #1's threat for a total of 51. That's still not greater than 101, so we move on to Character #3, who gains #2's threat for a total of 151. Now {{{_rand}}} <i>is</i> less than the threat value, so #3 is our target. If we didn't stack the values in this way, no one would be selected, because no one has a threat value greater than 101.
This threat system is based on the targeting system from <i>Dragon Age</i>, and has a number of built-in assumptions and limitations. You can read more about the implications of this system and other possibilities in [[Design]].
<h3 id="dispel">Dispel Targeting</h3>
Some enemies have abilities that can remove positive effects from player characters. It would be pretty silly if they used these abilities against characters with no buffs at all, so we need a mechanism to help them figure out which characters to target. This is the purpose of the widget {{{<<dispelTarget>>}}}.
{{{
<<widget "dispelTarget">>
<<set _threshold = $args[0]>>
<<set _go = false>>
<<set _continue = true>>
<<set _temp = 0>>
}}}
As usual, our default case will be that we do <i>not</i> find anything, so we set the {{{_go}}} flag to false. We also create an integer variable {{{_temp}}} for later use.
{{{
<<if !$args.includes("mass")>>
/* Martyr check */
<<set _martyrTest = $puppets.find(function (p) { return p && p.martyr === true })>>
<<if _martyrTest>> /* will be false if no martyr found */
<<for _effect range _martyrTest.effects.filter(function (eff) { return (eff.buff && !eff.sticky) })>> /* Search for buffs */
<<if _effect.name == "Blessing">>
<<set _temp += 3>> /* Blessings are higher priority to dispel */
<<elseif _effect.name == "Martyr" || _effect.name == "Defender" || _effect.name == "Berserker">>
/* do nothing; these expire on the next round anyway, so not worth it */
<<else>>
<<set _temp += 1>>
<</if>>
<</for>>
<<if _temp >= _threshold>>
<<set $B.target = _martyrTest>>
<<set _continue = false>>
<<set _go = true>>
<<else>>
<<set _continue = false>>
<</if>>
<</if>>
<</if>>
}}}
We then have to do a Martyr check, just like for regular targeting. If we {{{find}}} a martyr, we'll search through their effects array, filtered for dispellable (not "sticky") buffs. For every one of these, we increase {{{_temp}}} by 1. You can also give certain effects higher priority by checking for them with an if statement. In the default engine, the "Blessing" effect counts for 3 buffs, and everything else counts for 1.
The widget then makes a check against a threshold value. If the martyr has enough buffs to pass the threshold, we set them as the target, set {{{_go}}} to {{{true}}} to signal the dispel is viable, and stop the widget by setting {{{_continue}}} to {{{false}}}. If the martyr didn't pass the threshold, we just set {{{_continue}}} to false, because the attacker isn't allowed to keep going to check other targets if Martyr is active.
(Mass attacks ignore Martyr, so we also wrap this in a {{{if !$args.includes("mass")}}} conditional. This says that the Martyr check should only be run if the "mass" argument was <i>not</i> passed to the widget.)
If no Martyr was found, we continue on to normal targeting.
{{{
<<set _hitlist = []>>
}}}
First, we will make a hitlist, just like for regular targeting.
{{{
<<for _puppet range $puppets.filter(function (p) { return !p.dead; })>>
<<if !$args.includes("mass")>>
<<set _temp = 0>>
<</if>>
<<if $args.includes("ignore untargetable") || !_puppet.untargetable>>
<<for _effect range _puppet.effects.filter(function (eff) { return (eff.buff && !eff.sticky) })>> /* Search for buffs */
<<if _effect.name == "Blessing">>
<<set _temp += 3>> /* Blessings are higher priority to dispel */
<<elseif _effect.name == "Martyr" || _effect.name == "Defender" || _effect.name == "Berserker">>
/* do nothing; these expire on the next round anyway, so not worth it */
<<else>>
<<set _temp += 1>>
<</if>>
<</for>>
<</if>>
<<if !$args.includes("mass")>>
<<if _temp >= _threshold>>
<<run _hitlist.push(_puppet)>> /* If someone has a buff, make them a possible target */
<</if>>
<</if>>
<</for>>
}}}
We then create a for loop within a for loop. For every {{{_puppet}}} in {{{$puppets}}} who isn't dead and thus untargetable, we want to search through their effects array, just like we did in the Martyr check. This time, if they pass the threshold, they're added to the hitlist, making them a potential target for the dispel.
In <i>Cartoon Battle</i>, most dispel attacks have a threshold of 1, but you might find this clause useful if you create some reason to not use a dispel at every possible opportunity, such as if it costs the enemy some resource.
{{{
<<if (!$args.includes("mass") && _hitlist.length == 0) || ($args.includes("mass") && _temp < _threshold)>>
<<set _go = false>> /* If no one has any buffs, there's no point in using this; reroll */
<<else>>
<<set _go = true>>
<<if !$args.includes("mass")>> /* mass dispel doesn't need to pick a target */
<<set _n = random(0,(_hitlist.length-1))>>
<<set $B.target = _hitlist[_n]>>
}}}
From here, we check if we want the dispel to be used. If it's a non-mass dispel, we check to see if there's anything in {{{_hitlist}}}. If the length is zero, that means no one passed the threshold check, so there are no viable targets, and the dispel should not be used. For a mass dispel, we just check {{{_temp}}} against the given threshold. If either of these checks fail, we set {{{_go}}} to false to signal that the action should not be executed.
We end with a protection check, just as in {{{<<randomTarget>>}}}.
<h4 id="dispel.mass">Mass dispels</h4>
{{{
<<for _puppet range $puppets.filter(function (p) { return !p.dead; })>>
<<if !$args.includes("mass")>>
<<set _temp = 0>>
<</if>>
(...)
<<if !$args.includes("mass")>>
<<if _temp >= _threshold>>
<<run _hitlist.push(_puppet)>> /* If someone has a buff, make them a possible target */
<</if>>
<</if>>
}}}
Notice that at both the start and the end of the loop, there's an if clause to check if the flag "mass" was sent as an argument. If the dispel is only going to affect a single target, we want to track every character separately with {{{_temp}}}, so {{{_temp}}} has to be reset before each loop over a character's effect array, and then checked at the end before the widget moves on to the next character. For a mass dispel, we care about the total number of effects across the whole party, so we don't want to do either of these things. {{{_temp}}} is preserved across loops, and only checked after the greater {{{for}}} loop is finished.
<h3 id="targeting.ally">Ally Targeting</h3>
Some enemy actions target their allies instead of the player characters. We need something to handle that too.
{{{
<<widget "allytarget">>
<<set _hitlist = []>>
<<for _i, _enemy range $enemies.filter(function (e) { return !e.dead; })>>
<<if !($args.includes("noself") && _enemy == subject())>>
<<if $args.includes("buff")>>
<<if !_enemy.stasis>>
<<run _hitlist.push(_enemy)>>
<</if>>
<<else>>
<<run _hitlist.push(_enemy)>>
<</if>>
<</if>>
<</for>>
<<if _hitlist.length > 0>>
<<set _n = random(0,_hitlist.length-1)>>
<<set $B.target = _hitlist[_n]>>
<<unset _hitlist>>
<</if>>
<</widget>>
}}}
Fortunately, this functionality is much simpler. We can just reuse the basic functionality from our other targeting functions: search across the party, and if they're not dead, they're a viable target. A common-sense check is also run to exclude characters in Stasis if the ability conveys a buff.
For some abilities, you may want special behavior; a healing or protective effect should probably target the most injured character, for instance. One such special case, disallowing self-targeting, is implemented here. However, these aren't as generalizable as attacking abilities. You can implement this special behavior on a case-by-case basis in the actions database.
<h2 id="menus">Menu Functionality</h2>
A lot of RPGs involve managing characters and resources both in and out of battle. To do so requires some kind of menu where you can see your characters, items, equipment, etc. The functionality for these features can be found in the "party menu" file.
{{{
<span id="status">
<span style="display:inline-block; line-height:1.2; float:right; font-family:monospace">
[A] ↑<br/>
[D] ↓
</span>
<<for _n, _option range setup.MENU_OPTIONS>>
<<if _n != $menu_screen>>
<<capture _n, _option>>
<span @id="'menu'+_n">
<<link _option>>
<<set $menu_screen = _n>>
<<print '<<goto \"Menu: '+_option+'\">>'>>
<</link>></span><</capture>>
<<else>>
<b>_option</b>
<</if>>
<br/>
<</for>>
<span id="menu-return"><<longreturn>></span>
</span>
}}}
What makes the menu an actual menu is this little passage right here. This is {{{<<include>>}}}d at the top of every menu page, and creates a navbar the player can use to access different submenus. For modularity, this functionality is tied to {{{setup.MENU_OPTIONS}}}, a variable defined in StoryInit. You can modify it to easily add or remove menu screens without needing to change this passage, but note that you must follow its strict format: every menu passage must have the title "Menu: " followed by exactly what you wrote in {{{setup.MENU_OPTIONS}}}.
{{{<<longreturn>>}}} is a widget that can be found in "Widgets (General)". It functions like the {{{<<return>>}}} macro, but skips over passages tagged "noreturn". Since the player will be moving through multiple passages while navigating the menu, a regular {{{<<return>>}}} macro won't cut it -- it'll return them only to the previous menu passage, not the game area they opened the menu from. Make sure to tag all your menu passages "noreturn" so this works properly.
<h3 id="menus.status">Status</h3>
It's typical for RPG menus to include a general status screen that tells you relevant information about your characters, such as their stats and abilities. That's handled by this screen.
I won't copy the full code for this passage here, because it's mostly formatting and graphical design. I don't recommend modifying it unless you want to change how it looks and you're sure you know what you're doing.
By default, this screen mimics the appearance of RPG Maker VX Ace's menu screen. Each character's entry displays their portrait (if they have one), then their name and level, then their HP and MP, and then their experience points. By clicking the character's name or using the numbered hotkeys, you can expand the small info box into a more detailed one that provides additional information. Again, this is based on RPG Maker VX Ace's status screen: the character's stats are displayed on the left, and their current equipment on the right. Similarly to the in-battle status screen, you can define additional screens here using the {{{menu}}} property of the {{{setup.STATUS_SCREENS}}} variable. By default, the engine displays information for equipment, elemental affinities, and ailment tolerances; you'll have to design your own panes if you want to add others.
The character's available abilities are also listed below the detailed status screen. There is not currently functionality for using abilities outside of battle, but this is planned in a future update.
<h3 id="menus.inventory">Inventory</h3>
Most RPGs feature items and inventory management. It's therefore useful to give players a way to easily refer to their inventory.
Because of the sheer number of items it's possible to accumulate, this screen is more stripped-down compared to some of the others. The inventory list shows only the items and their stock values. To see more information, the player can click on an item to have its details appear at the top of the screen. This is accomplished by dividing the passage into two major parts, the inventory list and the detail display box:
{{{
<span id="content"><<nobr>>
<<set _filter = "all">>
<div id="itemdisplay">
<<include "inventory item display">>
</div>
<div id="itemlist">
<<include "inventory item list">>
</div>
<</nobr>>
</span>
}}}
Because we will be frequently updating these sections, it's helpful to offload their contents to separate passages so we can easily update them by {{{<<replace>>}}}ing them with themselves. Remember that Twine does not automatically update its passages after variable changes, so we need to refresh the content manually to reflect any changes we make.
{{{
<div style="display:flex; justify-content:space-evenly;">
<<if _filter == "all">>
<b>All</b>
<<else>>
<<link "All">>
<<set _filter = "all">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<<if _filter == "usable">>
<b>Usable</b>
<<else>>
<<link "Usable">>
<<set _filter = "usable">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<<if _filter == "equipment">>
<b>Equipment</b>
<<else>>
<<link "Equipment">>
<<set _filter = "equipment">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
</div>
}}}
The first thing we include in "inventory item list" is a set of filtering options. This helps the player find an item they need if they have a lot of items. By default, the filters are all items, usable items, and equippable items. These buttons are links that change the {{{_filter}}} variable and refresh the item list when clicked.
{{{
<div class="itemcontainer">
<<set _p = 2>>
<<for _name, _item range $inventory>>
<<if _filter == "all" || (_filter == "usable" && _item.usable.includes("inmenu")) || (_filter == "equipment" && _item.equippable)>>
<<if _p == 1>>
<<set _p = 2>>
<<elseif _p == 2>>
<<set _p = 1>>
<</if>>
<div @class="'item'+_p">
<b>
<<capture _item>>
<<link "_name">>
<<set _display = _item>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<</link>>
<</capture>>
</b> <span class="itemstock">(_item.stock)</span>
</div>
<</if>>
<</for>>
</div>
}}}
We then display the item list itself. The actual layout of the list is handled in {{{menu.css}}}; this code only handles the filtering and detail links. We display the items by running a {{{<<for>>}}} loop across the player's inventory, but for every iteration of the loop, we check against the filter: if "usable" is clicked, only items usable in the menu are shown, and if "equipment" is clicked, only items with equipment data are shown. The item name is then made into a link; when clicked, it copies the item to the {{{_display}}} variable and refreshes the display section.
{{{
<<if ndef _display || _display === null>>
<div style="text-align: center; line-height: 70px; color: gray">Click on an item to see info</div>
<<else>>
<b>_display.name</b><br/>
<<if def _display.equippable>><i style="font-size:12px"><<print _display.equippable.slot>></i><br/><</if>>
_display.info<br/>
<div class="actdesc">_display.desc</div>
<<if _display.usable.includes('inmenu') || _display.equippable>>
<div style="display: flex; justify-content: space-evenly">
<<set _b = 0>>
<<if _display.usable.includes('inmenu') && _display.stock > 0>>
<<set _b++>>
<span @id="'button'+_b">
<span id="usebutton">
<<include "inventory use button">>
</span>
</span>
<</if>>
<<if _display.equippable && _display.stock > 0>>
<<set _b++>>
<span @id="'button'+_b">
<span id="equipbutton">
<<include "equip button">>
</span>
</span>
<</if>>
</div>
<</if>>
<</if>>
}}}
The display section simply prints the item's data in an easily-readable fashion, provided you've selected an item to {{{_display}}}. (By default, it will direct you to click an item. We could simply not display the box at all until the player clicks on an item, but it's jarring for a new element to suddenly appear on the screen, especially when it appears at the top and pushes all the other elements on the page. Maintaining a default appearance for the box provides better aesthetic consistency and a smoother experience for the player.)
(Don't worry too much about the weird variables and IDs here, they're just to help direct the hotkeys in the rare circumstance that an item is both usable and equippable.)
The interesting part comes if the item is usable or equippable. In that case, we must provide functionality for the player to use or equip it. This functionality is, once again, offloaded to a set of child passages:
{{{
<<button "USE">>
<<set _itemcancel = true; _target = 1; _event = "use">>
<<if document.getElementById("usebutton")>><<replace "#usebutton">><<include "inventory cancel button">><</replace>><</if>>
<<if document.getElementById("equipbutton")>><<replace "#equipbutton">><<include "equip button">><</replace>><</if>>
<<replace "#itemlist">><<include "inventory puppets">><</replace>>
<</button>>
}}}
When the "USE" or "EQUIP" buttons are pressed, several things happen. Most of them are handlers for hotkeys or the rare case that an item is both usable and equippable, but the important event is that the button and the item list are replaced with new passages. We want to replace the button the player just clicked with a cancel button so that they can back out if they want to select a different item; but also, now that the player has selected an item to use, we need to provide a way for the player to use it.
{{{
<<set _HPmeters = []>>
<<set _MPmeters = []>>
<<for _i, _puppet range $puppets>>
<div class="menuactor" @id="_i" style="display:grid; grid-template-columns: 33% 1fr">
<div class="menuactor-nameblock" style="position:static; padding-left:14px; padding-top:5px; padding-bottom:0.5em; grid-row:1; grid-column:1; border-right:1px solid">
<div class="menuactor-name">
<<capture _i, _puppet>>
<<if _event == "equip">>
<<link _puppet.name>>
<<run _puppet.equip(_display)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<<elseif _event == "use">>
<<link _puppet.name>>
<<run _display.onUse(_puppet); console.log("item used"); console.log(_puppet.maxhp); console.log($puppets.indexOf(_puppet).maxhp)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<</capture>>
</div>
<div class="menuactor-stats" style="grid-row:unset; grid-column:unset; grid-template-columns:40% 3em 4em; border-right:none; top-padding:0">
<<for _k, _v range _puppet.stats>>
<div class="menuactor-statname" style="margin-left:0">_k</div>
<div class="menuactor-statvalue">_v.base</div>
<<if _v.bonus != 0>>
<div class="menuactor-statmod">
<<if _v.bonus > 0>>
@@.green;<<print "\+"+_v.bonus>>@@
<<elseif _v.bonus < 0>>
@@.stat-lowered;<<print "-"+_v.bonus>>@@
<</if>>
</div>
<</if>>
<</for>>
</div>
</div>
<<if _event == "equip">>
<div style="grid-row:1; grid-column:2; margin-left:1em; margin-top:0.5em; margin-bottom:0.5em; margin-right:1em">
<<for _k, _v range _puppet.equipment>>
<div class="menuactor-equipment-slot">_k</div>
<<if _v === null>><span class="menuactor-equipment-name"> </span>
<<else>>
<div class="menuactor-equipment-name">_v.name<br/>
<span class="actdesc">_v.info</span>
</div>
<</if>>
<</for>>
</div>
<<elseif _event == "use">>
<<set _id = 'hp'+_i>>
<<run _HPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<<set _id = 'mp'+_i>>
<<run _MPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.MP_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<div class="menuactor-hpmpblock" style="top:0.75em; width:260px">
<div class="hpmp">HP: <span style="float:right"><div class="hpmpvalue">_puppet.hp</div>/<div class="hpmpvalue">_puppet.maxhp</div></span></div>
<<showmeter _HPmeters[_i] `_puppet.hp / _puppet.maxhp`>>
<div class="hpmp">MP: <span style="float:right"><div class="hpmpvalue">_puppet.en</div>/<div class="hpmpvalue">_puppet.maxen</div></span></div>
<<showmeter _MPmeters[_i] `_puppet.en / _puppet.maxen`>>
</div>
<</if>>
</div>
<</for>>
}}}
We accomplish this by replacing the space occupied by the item list with "inventory puppets". This code may look complicated, but most of it is actually copied from the status screen, with a few tweaks for formatting. What's important is this part:
{{{
<<capture _i, _puppet>>
<<if _event == "equip">>
<<link _puppet.name>>
<<run _puppet.equip(_display)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<<elseif _event == "use">>
<<link _puppet.name>>
<<run _display.onUse(_puppet)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<</capture>>
}}}
Clicking on the character's name will use the item on them. However, we have to branch this functionality depending on if we're using or equipping the item, because those do different things. If we're equipping an item, we run {{{_puppet.equip}}}; if we're using an item, we run {{{_display.onUse}}}. In either case, we reset the {{{_display}}} variable and refresh the page so that the item list is displayed again.
{{{
<<if _event == "equip">>
<div style="grid-row:1; grid-column:2; margin-left:1em; margin-top:0.5em; margin-bottom:0.5em; margin-right:1em">
<<for _k, _v range _puppet.equipment>>
<div class="menuactor-equipment-slot">_k</div>
<<if _v === null>><span class="menuactor-equipment-name"> </span>
<<else>>
<div class="menuactor-equipment-name">_v.name<br/>
<span class="actdesc">_v.info</span>
</div>
<</if>>
<</for>>
</div>
<<elseif _event == "use">>
<<set _id = 'hp'+_i>>
<<run _HPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<<set _id = 'mp'+_i>>
<<run _MPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.MP_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<div class="menuactor-hpmpblock" style="top:0.75em; width:260px">
<div class="hpmp">HP: <span style="float:right"><div class="hpmpvalue">_puppet.hp</div>/<div class="hpmpvalue">_puppet.maxhp</div></span></div>
<<showmeter _HPmeters[_i] `_puppet.hp / _puppet.maxhp`>>
<div class="hpmp">MP: <span style="float:right"><div class="hpmpvalue">_puppet.en</div>/<div class="hpmpvalue">_puppet.maxen</div></span></div>
<<showmeter _MPmeters[_i] `_puppet.en / _puppet.maxen`>>
</div>
<</if>>
}}}
I also chose to branch the display, because the player should see information relevant to their decision. If you're equipping people, it stands to reason that you'll want to see what they already have equipped; but if you're using a healing potion, you'll probably want to see how everyone's health is doing instead.
<h3 id="partypicker">Party Picker</h3>
In some RPGs, you're only given a single party of characters to play with throughout the whole game. Some are a bit more ambitious, and give you additional reserve characters you can swap out with your active ones.
{{{
<span id="content"><center style="font-weight:bold">ACTIVE PUPPETS</center>
<<nobr>>
<div class="actors" id="puppets">
<<include "party manager puppets">>
</div>
<</nobr>>
<center style="font-weight:bold">RESERVE PUPPETS</center>
<<nobr>>
<div class="actors" id="reserve">
<<include "party manager reserve">>
</div>
<</nobr>>
<<unequipAll>>
</span>
}}}
This functionality is provided through the "Party Picker" passage, which you can access from the landing. Like the "Battle!" passage, the actual passage the player visits is only a container for complex code outsourced to other passages.
{{{
<<for _i, _puppet range $puppets>>
<div class="actor" @id="_i" style="min-width:150px">
<center>
<<capture _i>>
<<link _puppet.name>>
<<if def _s>>
<<run $('#' + _s).removeClass("selected")>>
<<if _s != _i>>
<<set _s = _i>>
<<run $('#' + _s).addClass("selected")>>
<<else>>
<<unset _s>>
<</if>>
<<else>>
<<set _s = _i>>
<<run $('#' + _i).addClass("selected")>>
<</if>>
<<replace "#status">><<include "party manager reserve">><</replace>>
<</link>>
<</capture>>
</center><br/>
<<for _k, _v range _puppet.stats>>
<span class="statname"><<print _k>>:</span>
<<statOOB _k _puppet>>
<br/>
<</for>>
</div>
<</for>>
}}}
Active puppets are displayed through the "party manager puppets" passage. Here, similarly to the actorlist, we create stat blocks for every puppet with a for loop. The key here is that we're going to need a unique ID for every block. We can do this with the {{{@}}} operator and the {{{_i}}} index variable of the for loop: every "actor" block will be given the ID 0, 1, or 2 as the loop runs. (Thanks to Twinery user greyelf for helping me with this.)
Within each block, we're going to {{{<<capture>>}}} the index value {{{_i}}} and create a {{{<<link>>}}} out of each puppet's name. When clicked, we're going to assign a separate value, {{{_s}}}, the value of {{{_i}}}, and then use a jQuery function to add the "selected" class to the current block. (It's theoretically possible to use SugarCube's {{{<<addclass>>}}} macro instead, but passing variables as IDs is a bit more difficult there.) The "selected" class just turns the block a bright cyan color, thus making it clear to the player that the puppet is selected.
{{{
<<run $('#' + _s).removeClass("selected")>>
}}}
We also need a quick line of code to remove the "selected" class from the <i>last</i> selected puppet, in case the player has already selected one. Otherwise, the cyan coloring wouldn't disappear, and the player might be confused who they're selecting.
We also need to update the "party manager reserve", for reasons that will be explained shortly.
The rest of the block can be anything; in the default engine, it's stats, since that's the major distinguishing feature of the default party members. You could include more info if you like; though you might need to change the size or display of the blocks to accommodate it.
{{{
<center>Reserve Puppets:</center><br/>
<<for _i, _puppet range $Reserve_Puppets>>
<span class="statname">
<<if def _s>>
<<capture _puppet, _i>>
<<link _puppet.name>>
<<run $Reserve_Puppets[_i] = $puppets[_s]>>
<<set $puppets[_s] = _puppet>>
<<unset _s>>
<<replace "#puppets">><<include "party manager puppets">><</replace>>
<<replace "#status">><<include "party manager reserve">><</replace>>
<</link>>
<</capture>>
<<else>>
_puppet.name
<</if>>
</span><br/><br/>
<</for>>
}}}
Finally, we need to let the player pick a party member from the reserve. As before, we iterate over a puppet array, but this time it's the {{{$Reserve_Puppets}}} array defined in {{{<<puppetsInit>>}}}. (To prevent the player from cloning people, {{{$Reserve_Puppets}}} shouldn't have any overlap with the active {{{$puppets}}} array.) If {{{_s}}} is defined (so, someone was selected), clicking their name will swap them with the selected character. Finally, we reset {{{_s}}} and refresh both character lists so the player can do it all again.
<h3 id="equipmanager">Equipment Manager</h3>
Though the player can already manage equipment through the inventory screen, I created a dedicated equipment screen for additional convenience. The conceit here is that while the inventory method was item-centric, this screen is character-centric. You select a character and then the equipment you want to give them, rather than the other way around.
{{{
<span id="content"><<unequipAll>>
<<nobr>>
<div id="puppets">
<<include "equip manager puppets">>
</div>
<div id="equipment-list">
<<equipmentlist>>
</div>
<</nobr>>
</span>
}}}
Similarly to our other menu passages, most of the functionality is offloaded to child passages. This passage is something of a hybrid of the status and inventory screens; we display the characters' status boxes up top, and the equipment down below.
The "equip manager puppets" passage is nearly identical to the status screen, but with equipment and stats displayed in place of HP/MP. When a character's name is clicked, a selector variable is similarly stored, which influences {{{<<equipmentlist>>}}}. That's a widget, not a passage, and can be found in "Widgets (Menu)".
{{{
<<if def _s>>
<div style="line-height:1.4">
<<set _count = 0>>
<<for _k, _v range $inventory>>
<<if _v.equippable && _v.stock > 0>>
<br/>
<<set _count++>>
<<if (def _s && _v.checkRestriction($puppets[_s])) || ndef _s>>
<div @id="_k">
<b>
<<if def _s>>
<<capture _v, _k>>
<<link _k>>
<<run $puppets[_s].equip(_v)>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<<replace "#equipment">><<equipmentlist>><</replace>>
<</link>>
<</capture>>
<<else>>
_k
<</if>>
</b>
<span style="float:right">(_v.stock)</span><br/>
<i style="font-size:12px"><<print _v.equippable.slot>></i><br/>
<<if _v.equippable.restrictedTo.length > 0>>
<span style="font-size:12px; color:gray">Restriction: <<for _char range _v.equippable.restrictedTo>><<print _char>><</for>></span>
<</if>>
<<print _v.desc>><br/>
<span class="actdesc"><<print _v.info>></span>
</div>
<</if>>
<</if>>
<</for>>
<<if _count == 0>>
You don't have any equipment.
<</if>>
</div>
<</if>>
}}}
This widget runs through the entire inventory ({{{<<for _k, _v range $inventory>>}}}) but will <b>only</b> display equippable items, thanks to the {{{<<if _v.equippable && _v.stock > 0>>}}} filter. Every time it finds an equippable item, it'll make a note of it through the {{{_count}}} variable; this is just so that we can confirm if the player has no equipment. (This is a courteous message to provide, as the player may just be confused if they try to pull up their equipment and see nothing at all.)
From there, it creates the link the player can use to equip the item.
{{{
<<if (def _s && _v.checkRestriction($puppets[_s])) || ndef _s>>
}}}
In English, this says: <b>If a character has been selected and that character isn't restricted from using this item <i>OR</i> no character is selected, execute the following code.</b> This removes items the selected character can't wear from the listing entirely, reducing the amount of chaff a player has to scroll through.
{{{
<<if def _s>>
<<capture _v, _k>>
<<link _k>>
<<run $puppets[_s].equip(_v)>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<<replace "#status">><<equipmentlist>><</replace>>
<</link>>
<</capture>>
<<else>>
_k
<</if>>
}}}
This says: <b>If a character has been selected, display the item's name as a link; otherwise, display the name as plain text.</b> Clicking the link will equip the item to the character, reset the selection, and update the screen so the player can see the difference.
Notice that this widget displays the full information of all equippable items -- something that was too expansive for the inventory screen, but a useful feature here, as it allows you to directly compare multiple pieces of equipment at once. Notice also that unlike the inventory screen, it does <b>not</b> reset the selector variable; this allows the player to equip multiple pieces of equipment all at once instead of having to go through the entire menu each time. So while this screen may not provide any truly unique functionality, it can still make the player's life easier.
<h3 id="itemshop">Item Shop</h3>
The item shop allows the player to alter the composition of their inventory.
{{{
Points: $currency<br/>
<br/>
<<for _k, _v range $inventory>>
<<if _v.usable == 0 || _v.usable == 1>>
<div>
<b>_k</b> (Stock: _v.maxstock)
<div style="display:inline-block; float:right">
<<capture _v>>
<<if $currency >= _v.value && _v.maxstock < 9>>
[<<link "BUY">>
<<replace "#items">>
<<run _v.maxstock++>>
<<run $currency -= _v.value>>
<<include "item shop display">>
<</replace>>
<</link>>]
<</if>>
<<if $currency >= _v.value and _v.maxstock > 0>> / <</if>>
<<if _v.maxstock > 0>>
[<<link "SELL">>
<<replace "#items">>
<<run _v.maxstock-->>
<<run $currency += _v.value>>
<<include "item shop display">>
<</replace>>
<</link>>]
<</if>>
<</capture>>
<b>Cost: _v.value</b>
</div>
</div>
<<print _v.info>><br/>
<span class="actdesc"><<print _v.desc>></span><br/>
<br/>
<</if>>
<</for>>
}}}
This code is very similar to the point-buy interface, which is explained in [[Additional Features]]. We display every one of the player's items with a {{{for}}} loop, similar to the {{{<<itemlist>>}}} widget. However, we also include BUY and SELL buttons. These buttons add or subtract from the item's stock and adjust the player's money based on the cost of the item, then refresh the item display to display the new value.
This functionality is rudementary, and designed for <i>Cartoon Battle</i>'s limited inventory system. For more complicated shop mechanics, such as shops that contain different items than those in the player's inventory, you may require separate tabs for buying and selling. If you have a lot of items to display, you may also want to defer the info and description displays.
<h2 id="hotkeys">Hotkeys</h2>
The engine's hotkeys are made through <a href="https://twinelab.net/custom-macros-for-sugarcube-2/#/./event-macros" target="_blank">Chapel's event macro set.</a> They are defined in StoryInit, but the code is compartmentalized into the "hotkey definitions" passage for modularity. After reading this section, you should have everything you need to edit the engine's hotkeys or add your own. The website <a href="https://keycode.info/" target="_blank">keycode.info</a> may be helpful for finding the keycode for your desired hotkeys.
There are way too many hotkeys to go over everything, and most of the code is very similar anyway. I'll use the Q key as our representative example.
I chose the Q key as the "confirm" key for the engine. This key does four things:
<ol>
<li>Select the "Act" link for the selected character in the command phase.</li>
<li>Select the default action for the character in the action selection phase.</li>
<li>Confirm the selection in the confirm phase.</li>
<li>Trigger any "Continue" buttons that occur in other battle phases, such as the buttons to advance battle after the action and enemy phases.</li>
</ol>
Let's look at how each of these features are built.
{{{
<<which 81>> /* Q pressed */
<<if $inbattle>>
<<switch $B.phase>>
<<case "command">>
<<if $B.subject !== null>>
<<set _id = "#actbtn a">>
<<trigger 'click' _id>>
<</if>>
<<case "actions">>
<<if def $B.subject.defaultAction && $B.subject.defaultAction !== null>>
<<set _action = $B.subject.defaultAction>>
<<actionLink>>
<</if>>
<<case "confirm">>
<<trigger 'click' "#confirmLink a">>
<<default>>
<<trigger 'click' "button.macro-button">>
<</switch>>
<<elseif passage() == "Menu: Inventory" && def _display && _itemcancel !== true>>
<<set _id = '#button1 button'>>
<<trigger 'click' _id>>
<</if>>
}}}
We start with {{{<<which 81>>}}}. The {{{<<which>>}}} macro tells the program to look for a key, and {{{81}}} is the code for the Q key. (It's handy to add comments for the corresponding key when you work with keycodes, to make things clearer for yourself or the end user.)
Because we want this key to have special functionality for battles, we check for the {{{$inbattle}}} flag before we tell it to execute any code. We also want the key to have different functionality depending on the phase of battle, so we create a {{{<<switch>>}}} for {{{$B.phase}}}.
Our first function is: <i>Select the "Act" link for the selected character in the command phase.</i>
{{{
<<case "command">>
<<if $B.subject !== null>>
<<set _id = "#act"+$puppets.indexOf($B.subject)+" a">>
<<trigger 'click' _id>>
<</if>>
}}}
This one is pretty simple. <a class="noExternal" href="#commands">We've already designed all the code for the Act button in the Battle Phases passage</a>; instead of copying all that over, we just need to make the link act as if it was clicked. We can do this through the {{{<<trigger>>}}} macro, but it needs to know what to target. Going back to {{{commands}}}, you can see we created IDs for each character's Act buttons for this exact purpose:
{{{
<span @id="'act'+_i"><<act $puppets[_i]>></span>
}}}
Each character's Act button has a unique ID that contains their index in the {{{$puppets}}} array. Because the {{{$B.subject}}} variable is an object from the {{{$puppets}}} array in the command phase, we can plug it into {{{indexOf}}} to find the selected character's index, and from there, pass the correct ID to the {{{<<trigger>>}}} macro.
There's just one wrinkle: We placed that ID on a {{{<span>}}} element surrounding the link, not on the link itself. If we were to just leave it at that, the {{{<<trigger>>}}} would only click that {{{<span>}}} element, which of course would do nothing. We need to provide {{{<<trigger>>}}} with one more bit of information: the specific element it needs to target. In this case, that's a link, which is an anchor {{{<a>}}} element. By appending that to the target argument (separated by a space), the {{{<<trigger>>}}} macro now knows to simulate a click on the anchor element <i>within</i> the element tagged "#act".
Lastly, things are going to get weird if {{{$B.subject}}} is {{{null}}}, so just to be safe, we also have to wrap this in an {{{<<if>>}}} that checks against that.
That's our first function done. Our next one is: <i>Select the default action for the character in the action selection phase.</i>
The setup we have to do for this function is to <i>create</i> a default action to reference in the first place. All of the default characters have one that's set in their constructors, but you can change this or even allow the player to select it themselves. (I caution against doing this with actions that can be changed or swapped out, such as Rogue's "Crossbow" and "Reload" actions, as the {{{defaultAction}}} property stores a separate copy that <b>will not be updated to reflect changes in the {{{actions}}} property</b> unless you do so manually.)
{{{
<<case "actions">>
<<if def $B.subject.defaultAction && $B.subject.defaultAction !== null>>
<<set _action = $B.subject.defaultAction>>
<<actionLink>>
<</if>>
}}}
For this one, I did choose to copy the action button code rather than use a {{{<<trigger>>}}}. You could make unique IDs for every single action link and do a search to find the one that matches the default action, but that was too much work for me. (This is why action button code is condensed into a widget: It allows for easy consistency between the two places it can be called.)
The player will get no notification if the action can't be used for whatever reason; the code simply won't execute. I doubt players will be too confused by this, as they can look at the action button itself to see why they can't use it, but you could also set an audio or visual indicator to play when this happens, as most video games do.
As before, we wrap the whole thing in an {{{<<if>>}}} to make sure our crucial variable exists before we start executing code on it.
Our next function is: <i>Confirm the selection in the confirm phase.</i>
{{{
<<case "confirm">>
<<trigger 'click' "#confirmLink a">>
}}}
This one is trivial compared to the others. You may be tempted to forego the {{{<<trigger>>}}} entirely and just use {{{<<goto "action phase">>}}}, and that will work in the default engine, but there may be cases where you want the confirm link to perform additional code or processing, so we make sure to trigger it just to be safe.
Out last function is: <i>Trigger any "Continue" buttons that occur in other battle phases, such as the buttons to advance battle after the action and enemy phases.</i>
{{{
<<default>>
<<trigger click "button.macro-button">>
}}}
For this one we do want to use {{{<<trigger>>}}} -- it's easier to use than before, as we're targeting general elements instead of an ID we have to make ourselves. In Twine, there is one additional wrinkle here: We want to make sure we're only activating the buttons inside the Twine game, and not anything outside of it. This is an important distinction, because the UI sidebar also contains {{{<button>}}} elements that will be activated by something that targets all buttons. Fortunately, SugarCube automatically labels all macros and elements within the game area with classes labeling them as such, so we only need to add a {{{.macro-button}}} extension to limit our trigger to buttons within the game area.
<h3 id="hotkeys.summary">Summary</h3>
To do anything with key presses, create an {{{<<event keydown>>}}}.
Create functions for each key under {{{<<which [keycode]>>}}}. Any code within will be executed on the key press, the same as if the user had clicked a {{{<<link>>}}}.
Use {{{<<trigger>>}}} to trigger existing events on the page, but remember that the code is effectively "blind" and can only see specific elements if you give them an ID. Remember also that SugarCube's macros generate their own elements that you can't directly modify, so append an element target to your ID if you want to trigger one.
The UI bar contains HTML elements that can be affected by hotkeys. If you want to restrict hotkeys to elements within the game area, inspect them with the Inspect functionality (right-click or Ctrl+Shift+I) to see their unique classes and append them to your target. (Typically, these classes are some variation of "macro" or "internal".)
<h2 id="widgets">Other Widgets</h2>
This section will cover miscellaneous widgets and their purpose.
<h3 id="chain">The {{{<<chain>>}}} widget</h3>
Twine has a difficult quirk: object addresses are not preserved through passages.
[img[setup.ImagePath + "documentation/variable_types.PNG"]]
Normally, when you define a variable, the value of the variable is stored in the computer's memory at the variable's address. When you define an object, the variable you set as its name does not store the object itself, only the memory address. The object variable therefore serves as a pointer to the actual object. This means that you can pass the object's name to other variables and still have those new variables point to the same object.
For instance, if we wanted to store the currently acting character in a "subject" variable, we might do this:
{{{
<<set $subject = $puppets[1]>>
}}}
If we did this in a standard coding engine, {{{$subject}}} would refer to the same object as {{{$puppets[1]}}}, and everything we did to {{{$subject}}} would also affect {{{$puppets[1]}}}.
Twine, for various reasons, does not work this way. Every time the user travels to a new passage, a new copy of every object is generated, and this breaks shared object references. In our example, {{{$subject}}} would now point to a completely new object, and acting on it would not affect our {{{$puppets[1]}}} object.
{{{
<<widget "chain">>
<<if $B.target !== null>>
<<run getActor("target")>>
<</if>>
<<if $B.subject !== null>>
<<run getActor("subject")>>
<</if>>
<<if $B.actor !== null>>
<<run getActor("actor")>>
<</if>>
<</widget>>
window.getActor = function getActor(x) {
var targets = [];
var id;
var actor;
switch(x) {
case "target": id = V().B.target.id; break;
case "subject": id = V().B.subject.id; break;
case "actor": id = V().B.actor.id; break;
default: console.log("ERROR in getActor: invalid argument");
}
switch(id.charAt(0)) {
case "p": targets = State.variables.puppets; break;
case "e": targets = State.variables.enemies; break;
default: console.log("ERROR: Target ID does not match any known party.");
}
actor = targets.find(function(t) { return t && t.id === id; });
switch(x) {
case "target": V().B.target = actor; break;
case "subject": V().B.subject = actor; break;
case "actor": V().B.actor = actor; break;
default: console.log("ERROR in getActor: invalid argument");
}
return;
}
}}}
The {{{<<chain>>}}} widget (found in the "General Widgets" passage) and the {{{getActor()}}} function (found in 0config.js) are a workaround for this issue. It is based on the fact that shared object references still work <i>within the same passage</i>. If we were to run {{{<<set $subject = $puppets[1]>>}}} at the start of every passage, {{{$subject}}} would behave the way we want.
This is essentially what {{{getActor()}}} does. Every character is assigned an ID based on a randomly generated value when their instance is created; by reading this attribute in the stored object, we can find the matching character at any time using the {{{find}}} function, which returns the entry in an array matching a key value (in this case, {{{id}}}).
One additional wrinkle is that we need to know the correct array to search -- {{{$puppets}}} or {{{$enemies}}}. To facilitate this, there's a special feature in the ID generation code that adds a fixed symbol at the start of the ID depending on whether the character is a puppet or enemy; all we need to do is check that marker and we know which array to search.
{{{
<<if $inbattle>>
<<chain>>
<</if>>
}}}
{{{<<chain>>}}} is then added to PassageReady, a special passage that runs at the beginning of every passage. This lets {{{$B.subject}}}, {{{$B.target}}}, and {{{$B.actor}}} retain their object links through passages, which greatly simplifies many widgets.
<h3 id="refreshPuppets">{{{<<refreshPuppets>>}}}</h3>
{{{
<<widget "refreshPuppets">>
<<for _puppet range $puppets>>
<<set _puppet.isDone = false>>
<<set _puppet.inspired = false>>
<<set _puppet.lastUsed = null>>
<<if !$lastingDamage || $reviveAfterBattle>>
<<set _puppet.dead = false>>
<<set _puppet.hp = Math.round(_puppet.maxhp * setup.RESPAWN_HP)>>
<</if>>
<<if !$lastingDamage>>
<<set _puppet.hp = _puppet.maxhp>>
<<set _puppet.en = 5>>
<</if>>
<<if !$actionRefillAfterBattle>>
<<for _action range _puppet.actions.filter(function (a) { return a.uses !== undefined })>>
<<run _action.refill()>>
<</for>>
<</if>>
<<for _effect range _puppet.effects.filter(function (eff) { return !eff.persistAfterBattle })>>
<<run _puppet.removeEffect(_effect,{pierce: true, unsticky: true})>>
<</for>>
<<for _k, _v range _puppet.tolerances>>
<<run _puppet.resetTol(_k)>>
<</for>>
<<if def _puppet._respawn>>
<<run _puppet._respawn.refill()>>
<</if>>
<<if def _puppet._retaliations>>
<<run _puppet._retaliations.refill()>>
<</if>>
<<set _x = _puppet.actions.find(function (a) { return a && a.name == "Reload" })>>
<<if def _x>>
<<set $B.subject = _puppet; _x.act()>>
<</if>>
<</for>>
<</widget>>
}}}
{{{<<refreshPuppets>>}}} is defined in "Widgets (General)". This widget is used to standardize the states of player characters before and after battles. Flags such as {{{isDone}}} are reset, non-persistent status effects are cleared, {{{FillStat}}}s are refilled, and Rogue's crossbow is reloaded if it's empty.
There is modifiable behavior depending on if you want puppets to restore HP after battle and to what extent. By default, puppets will be restored to full HP and have their EN reset to baseline. If you don't want this to happen, you can use the {{{lastingDamage}}} variable to bypass it.
For additional nuance, you can also decide whether you want puppets to be revived after battle, regardless of if they're normally healed. You can enable this even with {{{lastingDamage}}} active through the {{{reviveAfterBattle}}} variable. By default, puppets will be revived to a proportion of the max HP determined by {{{RESPAWN_HP}}}. Note that this comes before the standard HP reset, meaning that they will be restored to full HP anyway if lasting damage is not enabled. There is also a separate variable that determines if you want limited-use actions to be refilled after battle.
<h3 id="deathcheck">{{{<<deathcheck>>}}}</h3>
{{{
<<if $args.length > 0>>
<<set _targ = $args[0]>>
<<else>>
<<set _targ = target()>>
<</if>>
<<if _targ.hp <= 0 && !_targ.dead>>
<<if _targ.specialdeath && !$B.specialdeath.includes(_targ._deathMessage)>>
<<run $B.specialdeath.push(_targ._deathMessage)>>
<<elseif !_targ.immortal>>
<<set _targ.dead to true>>
<<if _targ._deathMessage !== null>>
<<print _targ.deathMessage>>
<</if>>
<<if _targ instanceof Enemy>>
<<run $B.kills.push(_targ)>>
<<if typeof(subject().kills) == 'number'>>
<<set subject().kills++>>
<</if>>
<<set $B.XPreward += _targ.xp; $B.moneyReward += _targ.gp>>
<<elseif _targ instanceof Puppet>>
<<run _targ.defeats++>>
<</if>>
/* Remove all effects unless they are meant to persist past unconsciousness */
<<for _k, _effect range _targ.effects.filter(function (eff) { return !eff.persistAfterDeath; })>>
<<run _targ.removeEffect(_effect,{pierce: true, unsticky: true})>>
<</for>>
/* If defeated character was protected, their protector's protection effect must be removed */
<<if _targ.protectedBy !== null>>
<<switch _targ.protectedBy.charAt(0)>>
<<case "p">>
<<set _targets = $puppets>>
<<case "e">>
<<set _targets = $enemies>>
<<default>>
<<run console.log("ERROR in deathcheck: protected character's protector has invalid ID")>>
<</switch>>
<<set _temp = _targets.find(function(t) { return t && t.id === _targ.protectedBy; })>>
<<find "_temp.effects" "name" "\'Protector\'">>
<<print _temp.removeEffect(_temp.effects[_pos],{pierce: true})>>
<</if>>
<<elseif _targ.immortal>>
<<if _targ._deathMessage !== null>>
<<print _targ.deathMessage>>
<</if>>
<</if>>
<</if>>
<</widget>>
}}}
This widget applies the "dead" tag to defeated characters, and adds their names to the "kills" array in case that's something you want to keep track of.
{{{
<<if _targ.specialdeath && !$B.specialdeath.includes(_targ._deathMessage)>>
<<run $B.specialdeath.push(_targ._deathMessage)>>
}}}
The first thing is a check for characters with special death messages. You might want certain enemies to have a full passage's worth of description when they're defeated, such as if they're a climactic villain. If you set their {{{specialdeath}}} attribute to {{{true}}}, the player will be directed to the passage with a name matching their {{{deathMessage}}}. (To prevent possible stacking of these passages, which can occur if this code is triggered multiple times at once, such as from multi-hit attacks, there is also a check that their special death isn't in the queue already.)
Otherwise, as long as the target isn't flagged as immortal, the normal death message will be displayed and the target will be flagged as dead. (Note that this requires the target be set correctly! Usually this won't be a problem, as the damage widgets require a target as well. However, you'll need to update the {{{$B.target}}} value or pass an argument any time a character could be damaged, such as from damage-over-time effects.) If the target <i>is</i> immortal, only their death message is displayed.
{{{
<<if _targ instanceof Enemy>>
<<run $B.kills.push(_targ)>>
<<if typeof(subject().kills) == 'number'>>
<<set subject().kills++>>
<</if>>
<<set $B.XPreward += _targ.xp; $B.moneyReward += _targ.gp>>
<<elseif _targ instanceof Puppet>>
<<run _targ.defeats++>>
<</if>>
}}}
If the target was an Enemy, we add them to the "kills" array held by the battle controller, if that's something you want to keep track of. (For example, in <i>Cartoon Battle</i>, there were achievements based on which enemy you defeated last.) We also increment the subject's "kills" attribute, though we have to check to make sure it's the correct data type just in case something weird has happened and we'll produce an error by trying to increment it. (One possible example might be an enemy killing another enemy; enemies do not have a defined <code>kills</code> attribute by default, so this will generate an error.) Finally, we add the enemy's XP and GP rewards to the total that will be read at the end of battle.
If the target was a Puppet, we increment their "defeats" counter.
{{{
<<if _targ.protectedBy !== null>>
<<switch _targ.protectedBy.charAt(0)>>
<<case "p">>
<<set _targets = $puppets>>
<<case "e">>
<<set _targets = $enemies>>
<<default>>
<<run console.log("ERROR in deathcheck: protected character's protector has invalid ID")>>
<</switch>>
<<set _temp = _targets.find(function(t) { return t && t.id === _targ.protectedBy; })>>
<<find "_temp.effects" "name" "\'Protector\'">>
<<print _temp.removeEffect(_temp.effects[_pos],{pierce: true})>>
<</if>>
}}}
There is also a special handler here for the "Protector" status effect. If the person the protector is protecting dies, it no longer makes sense to keep the effect. We use the target's {{{protectedBy}}} attribute to see if they were being protected by anyone and find their protector if so, then we find the Protector effect in their {{{effects}}} array and remove it.
<h3 id="statusDoc">{{{<<status>>}}}</h3>
{{{
<<widget "status">>
<<link "[*]">>
<<if $args[0] eq $B.actor>>
<<set $B.actor = null>>
<<else>>
<<set $B.actor to $args[0]>>
<<if ndef $stScreen>>
<<set $stScreen = 1>>
<</if>>
<</if>>
<<replace "#status">><<include "status">><</replace>>
<</link>>
<</widget>>
}}}
This widget creates that little [*] button you use to get information on characters.
You may recall from the "actorlist" passage that the widget is called like this:
{{{
<<status _enemy>>
}}}
That passes the character's data to the widget as {{{$args[0]}}}. So, when the {{{<<link>>}}} macro is activated (i.e., when the [*] is clicked), the widget first checks if the passed information matches what's already stored in {{{$B.actor}}}. If it does, {{{$B.actor}}} is set to null, which clears the information in the status pane. This is how the status button is "toggleable": if you click it once, you get that character's information; if you click it again for the same character, the pane clears.
By default, {{{$actor}}} is set to the information for the new character, and the {{{$stScreen}}} value is set to 1 (defaulting to the pane displaying stats). A {{{<<replace>>}}} macro is then used to refresh the status pane to display the new character.
Recall that {{{$B.actor}}} is one of the variables run by {{{<<chain>>}}}, so the status pane will update to reflect changes to the character.
<h3 id="endofbattle">{{{<<endofbattle>>}}}</h3>
{{{
<<widget "endofbattle">>
<<set $inbattle = false>>
<<for _id range $B.playerBars>>
<<run Meter.del(_id)>>
<</for>>
<<for _id range $B.enemyBars>>
<<run Meter.del(_id)>>
<</for>>
<<if deadCount() == $puppets.length>>
<<set $B.defeated = true>>
<<elseif deadCount() == $puppets.length-1>>
<<set $B.SoleSurvivor = true>>
<<elseif deadCount() == 0>>
<<set $B.Perfect = true>>
<</if>>
<<if !$B.defeated>>
<<set $encounters[$scenario] = true>>
<</if>>
<<if $B.style>>
<<addclass "body" $B.style>>
<<removeclass "html" $B.style>>
<</if>>
<<refreshPuppets>>
<<include "custom end of battle">>
<<unset $B; $enemies>>
<</widget>>
}}}
This widget is called to clean up variables after battle. It resets {{{$inbattle}}} to {{{false}}}, clears the health meters, battle controller, and {{{$enemies}}} variable since we no longer need them, flags the encounter as done in the {{{$encounters}}} object, resets the style if there was one, and restores puppets to their resting states (see {{{<<refreshPuppets>>}}}).
There is also a block that records how many puppets were defeated in the battle. This was used for calculating some achievements in <i>Cartoon Battle</i>; you may use it for whatever purposes you wish. Note, however, that since {{{$B}}} is cleared at the end of this widget, you will have to record these flags to another variable in "custom end of battle" if you wish to use them past this point.
By default, the only thing in "custom end of battle" is a call to the {{{<<restock>>}}} widget, which replenishes all items.
<h3 id="backbtn">{{{<<backbtn>>}}}</h3>
{{{
<<widget "backbtn">>
<span class="btn back right" id="battlebackbtn">[[Back [R]|Battle!][$B.phase = "command"; $B.targeting = null; $action = null; $B.target = null; $B.subject = null; $removed_effects = [];]]</span>
<</widget>>
}}}
The {{{<<backbtn>>}}} widget (an abbreviation for "back button") is the gray "BACK" button you see in battle. It's just a link back to the battle hub, plus a whole host of variable assignments to reset any flags triggered while selecting actions, such as {{{$B.target}}}. If this wasn't done, flags might be able to "bleed over" into other actions when they weren't supposed to -- enabling you to select characters to attack outside of the targeting phase, for instance.
<h3 id="victorycheck">{{{<<victorycheck>>}}}</h3>
{{{
<<widget "victorycheck">>
<<set _victory to 0>>
<<set _defeat to 0>>
<<for _enemy range $enemies>>
<<if _enemy.dead>>
<<set _victory++>>
<</if>>
<</for>>
<<if _victory eq $enemies.length>>
<<set $B.victory = true>>
<<goto "Victory">>
<</if>>
<<for _puppet range $puppets>>
<<if _puppet.dead>>
<<set _defeat++>>
<</if>>
<</for>>
<<if _defeat eq $puppets.length>>
<<set $B.defeat = true>>
<<goto "Defeat">>
<</if>>
<</widget>>
}}}
You need to have some handler to determine when your player wins or loses the game. This widget, called at the start of the "Battle!" passage (so, constantly throughout the battle) runs over the enemy and ally parties and uses a temporary variable to track how many have been defeated. If the dead characters equal the length of the array (that is to say, all the characters have been defeated), the player is immediately moved to a new passage: "Victory" for the enemies' defeat, and "Defeat" for the player's defeat.
Note that the check and move for the "victory" handler is performed before the "defeat" check. This means that if the player and enemy are defeated simultaneously, the win will default to the player. This situation is very rare, as it requires an action that hurts both sides, but if you plan to implement actions like that, it is a good idea to think about how you want that situation to be handled.
{{{
<<set $B.victory = true>>
(...)
<<set $B.defeat = true>>
}}}
Additionally, we need to explicitly set a flag when victory or defeat has happened. This is because of <a class="noExternal" href="#auto-end">the auto-endturn feature</a>; because it also uses a forced passage change, it is possible for the {{{<<goto "Victory">>}}} and {{{<<goto "end of round">>}}} commands to stack, sending the player in a loop that causes strange effects.
<h3 id="stat">{{{<<stat>>}}}</h3>
{{{
<<widget "stat">>
/* Designed by greyelf */
/* Check that a Stat Name was passed to the widget. */
<<if $args.length is 0>>
ERROR
<<else>>
<<set _current to $B.actor.get($args[0]) >>
<<set _base to $B.actor.getBase($args[0]) + $B.actor.getBonus($args[0]) >>
/* Check if the Stat has been raised. */
<<if _current > _base >>
@@.stat-raised;_current@@
/* Check if the Stat has been lowered. */
<<elseif _current < _base >>
@@.stat-lowered;_current@@
/* The Stat has not changed. */
<<else>>
_current
<</if>>
<</if>>
<</widget>>
}}}
This widget was provided by Twinery user greyelf in response to one of my questions. It enables the conditional coloring of the stats that display in the status pane depending on if they've been raised or lowered by stat mod effects.
The {{{@@}}} symbols here modify webpage elements. The first line adds the given class to the current element, and the second line (after the semicolon) populates the current element with text. In this case, it always populates the element with {{{_current}}}, but adds the {{{.stat-raised}}} class if the stat has been raised, and adds the {{{.stat-lowered}}} class if the stat has been lowered. The rules for these classes are defined in the story stylesheet; they color the text blue and red, respectively.
<h3 id="itemdrop">{{{<<itemDrop>>}}}</h3>
{{{
<<widget "itemDrop">>
<<set _item = new Item($args[0])>>
<<if def $args[1] && typeof($args[1]) == 'number'>>
<<set _amt = $args[1]>>
<<else>>
<<set _amt = 1>>
<</if>>
<<set _added = $inventory.addItem($args[0],_amt)>>
<center>
<div class="itembox">
<b>_item.name<span style="float:right">x<<print _amt>></span></b><br/>
<<print _item.info>><br/>
<span class="actdesc"><<print _item.desc>></span>
</div>
<<if _added === false>>
<i>You can't hold any more of these! Use or sell some and then come back.</i>
<</if>>
</center>
<</widget>>
}}}
This widget provides a standardized method for informing the player they've received an item. Pass the name of the item as the first argument and the amount as the second. If no amount is set, it will default to 1. The widget then displays the item's information in a fancy box, and notifies the player if they've run out of inventory space.
<h3 id="musicwidgets">Music widgets</h3>
{{{
<<widget "playMusic">>
<<if $args.length > 0 && typeof($args[0]) == 'string'>>
<<if !SimpleAudio.tracks.get(_trackId).isPlaying()>>
<<audio ":playing" stop>>
<</if>>
<<set $music = new Music($args[0])>>
<<set _trackId = $args[0].split(' ').join('_')>>
<<if !$args.includes("instant") && !SimpleAudio.tracks.get(_trackId).isPlaying()>>
<<script>>
/* code provided by The Mad Exile */
var selector = State.temporary.trackId;
setTimeout(function () {
SimpleAudio.select(selector)
.loop(true)
.volume(1)
.play();
}, 500); /* in milliseconds */
<</script>>
<<else>>
<<audio _trackId volume 1 play loop>>
<</if>>
<<if document.getElementById("musicInfo")>>
<<replace "#musicInfo">><<include "music info">><</replace>>
<</if>>
<<else>>
<<run console.log("ERROR in playMusic: non-string argument passed")>>
<</if>>
<</widget>>
<<widget "clearMusic">>
<<audio ":playing" volume 1 stop>>
<<unset $music>>
<<if document.getElementById("musicInfo")>>
<<replace "#musicInfo">><<include "music info">><</replace>>
<</if>>
<</widget>>
}}}
These widgets handle the playing and stopping of background music. Their main purpose is to sync the current track with the {{{music}}} variable, which is used to display information for the currently playing track in the sidebar, but there are some other functions as well.
{{{
<<if !SimpleAudio.tracks.get(_trackId).isPlaying()>>
<<audio ":playing" stop>>
<</if>>
}}}
The start of the widget stops all currently playing audio aside from the called track, because generally, you only want one background music track playing at a time. (If you plan to use other audio, such as sound effects, you may want to create a "BGM" audio group and limit your {{{stop}}} command to that group.)
{{{
<<set $music = new Music($args[0])>>
}}}
The {{{Music}}} class is defined in the {{{database-music.js}}} file. There, you can create a database mapping composer, license, album, and etc. to the track name, instead of needing to set all of that information manually every time you change a track.
{{{
<<set _trackId = $args[0].split(' ').join('_')>>
}}}
Track names in SugarCube cannot contain spaces. Standard practice is to substitute underscores instead. This code allows the program to correctly call the track ID while allowing you to input the name with spaces, for convenience.
{{{
<<script>>
/* code provided by The Mad Exile */
var selector = State.temporary.trackId;
setTimeout(function () {
SimpleAudio.select(selector)
.loop(true)
.volume(1)
.play();
}, 500); /* in milliseconds */
<</script>>
}}}
This is raw JavaScript code that makes a call to the {{{SimpleAudio}}} feature, which controls audio in SugarCube. The important feature here is that it adds a half-second delay. Audio processing can sometimes happen too fast in Twine, starting the track before the passage has finished rendering. This short delay typically allows the audio to sync up more nicely with the passage loading times, especially when calling battle music.
{{{
<<if !$args.includes("instant") && !SimpleAudio.tracks.get(_trackId).isPlaying()>>
(...)
<<else>>
<<audio _trackId volume 1 play loop>>
<</if>>
}}}
If you don't want it, though, you can pass the "instant" argument to the widget, which will call the normal {{{<<audio>>}}} macro.
{{{
<<if document.getElementById("musicInfo")>>
<<replace "#musicInfo">><<include "music info">><</replace>>
<</if>>
}}}
Finally, we refresh the music info in the sidebar to update it to display our new track.
{{{<<clearMuisc>>}}} is much simpler, and just stops the current audio while erasing the {{{music}}} variable.
<h3 id="find">{{{<<find>>}}}</h3>
{{{
<<widget "find">>
/* args0 is target array, args1 is attribute, args2 is key value */
/* Note that if your key is a string, it must be in quotes in the final code, which means you must send it to the widget with an extra pair of literal quotes (preceeded by the escape slash) */
<<print '<<set _pos = '+$args[0]+'.map(function(x) { return(x.'+$args[1]+') }).indexOf('+$args[2]+')>>'>>
<</widget>>
}}}
This widget was made early in the engine's design, before I knew about JavaScript's {{{Array.find}}} function. It essentially replicates this functionality, but in most cases you are better off just using {{{find}}}.
What you see here is a JavaScript function that searches through an array of objects and returns the index of a given attribute that matches a given value. The array, the attribute, and the value are given as arguments to the widget, in that order. After it's done, it'll store the index value in {{{_pos}}} for you to use elsewhere. If it didn't find anything, {{{_pos}}} will be set to -1.
Why is it wrapped in a {{{<<print>>}}} statement? This is something the Twine community calls the "Stupid Print Trick", because it looks stupid, but it actually works. If you use the {{{<<print>>}}} statement to print code, that code will be executed. Because {{{<<print>>}}} can concatenate strings, this is handy for creating code with variable parameters. The only trick is that every one of the arguments has to be a string literal (surrounded by quotes), or it won't work. The other caveat is that if you're searching for a string value, that value must <i>display in the code</i> as a string literal, which requires you to add an extra pair of quotes. You must do this by using the escape slash {{{\}}} character so they're parsed as literal.
If you didn't understand that, don't worry! Just follow the directions in the comment and you should be fine.Welcome to Another RPG Engine, an RPG engine made in Twine. This engine is designed for deterministic multi-character battles, like you might see in RPG Maker or Japanese-style RPGs. I created a simple game with this engine called <i><a href="https://anotherrpgenthusiast.itch.io/cartoon-battle" target="_blank">Cartoon Battle</a></i> that I encourage you to try. I may make more complex games using this engine in the future!
Click on the links above to learn about aspects of the engine in more detail.
Download the source code and discuss the game at <a href="https://anotherrpgenthusiast.itch.io/another-rpg-engine" target="_blank">itch.io</a>. You can also discuss the game on <a href="https://archiveofourown.org/works/19398175" target="_blank">Archive of Our Own</a> if that's more your wheelhouse, and you can post your games there or on itch.io!
Example battles:
* [[Princess Bubblegum (Adventure Time)|Preparation][$scenario = "at1"]]
* [[Mystery Twins (Gravity Falls)|Preparation][$scenario = "gf1"]]
* [[Watterson Kids (The Amazing World of Gumball)|Preparation][$scenario = "gum1"]]
* [[Crystal Gems (Steven Universe)|Preparation][$scenario = "su1"]]
[[Here is the party menu|Menu: Status][$menu_screen = 0]] if you'd like to change your puppets or their equipment.
Animation tests:
* [[Single hit attack|Anim Test][$subtest = "Sword"]]
* [[Multi-hit attack|Anim Test][$subtest = "Knife"]]
* [[AoE|Anim Test][$subtest = "Red Tide"]]
* [[Grenade|Anim Test][$subtest = "Grenade"]]
* [[Weakpoint|Anim Test][$subtest = "Azure Frost"]]
* [[Immunity|Anim Test][$subtest = "White Light"]]
* [[Heal|Anim Test][$subtest = "Gold Sparks"]]
* [[Shield Effect|Anim Test][$subtest = "block"]]
* [[Ailment|Anim Test][$subtest = "Insult"]]
* [[Hit and ailment|Anim Test][$subtest = "Flurry"]]
* [[Remove ailment|Anim Test][$subtest = "Antidote"]]
* Regeneration ([[light|Anim Test][$subtest = "regen"]], [[dark|Anim Test][$subtest = "regen dark"]])
* [[Thorns|Anim Test][$subtest = "Thorns"]]
Here is a link to the [[Point-buy interface example]] for testing.
Here is a link to the [[Item Shop]] for testing.
Here is a link to the [[Decurse Station]] for testing.
<<link "Start Music">><<playMusic "Happy 8bit Loop 01">><<goto "Start">><</link>>
You can change the difficulty through the Settings button in the sidebar. Keep in mind that this is more of a randomness setting than a difficulty setting.<span style="font-size:12px">
* Hard: smart targeting will only target vulnerable characters
* Medium: smart targeting will preferentially target vulnerable characters, but other targets can be considered
* Easy: disables smart targeting
</span><div id="navbar">
<span><b>Version: <<print setup.version>></b> ([[Changelog]])</span>
<div>
<span style="font-weight:bold"><<if passage() == "Start">>Home<<else>>[[Home|Start]]<</if>></span>
<span><<if passage() == "Installation">><b>Installation</b><<else>>[[Installation]]<</if>></span>
<span>Documentation (<<if passage() == "Documentation (Basic)">><b>Basic</b><<else>>[[Basic|Documentation (Basic)]]<</if>>, <<if passage() == "Documentation (Advanced)">><b>Advanced</b><<else>>[[Advanced|Documentation (Advanced)]]<</if>>)</span>
<span><<if passage() == "Design">><b>Design</b><<else>>[[Design]]<</if>></span>
<span><<if passage() == "Additional Features">><b>Additional Features</b><<else>>[[Additional Features]]<</if>></span>
</div>
</div>
<hr/><<widget "backbtn">>
<span class="btn back right" id="battlebackbtn">[[Back [R]|Battle!][$B.phase = "selection"; $B.targeting = null; $action = null; $B.target = null; $B.subject = null; $removed_effects = [];]]</span>
<</widget>><<widget "endturn">>
<<set $B.turn = "enemy"; $B.phase = null>>
<<goto "end of round">>
<</widget>><<widget "status">>
<<link "[*]">>
<<if $args[0] eq $B.actor>>
<<set $B.actor = null>>
<<else>>
<<set $B.actor to $args[0]>>
<<if ndef $stScreen>>
<<set $stScreen = 1>>
<</if>>
<</if>>
<<replace "#status">><<include "status">><</replace>>
<</link>>
<</widget>><<widget "act">>
/* arg 0 = puppet to command */
<<if $args[0].down is true>>
<<if $args[0].en >= setup.STRUGGLE_COST>>
<<link "Struggle">>
<<set $B.subject = $args[0]; $action = new Action("struggle"); $B.target = null>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<<else>>
<b>Exhausted!</b>
<</if>>
<<else>>
<<link "Act">>
<<set $B.subject = $args[0]>>
<<replace "#phase">><<include "actions">><</replace>>
<</link>>
<</if>>
<span class="hotkey monospace right">[Q]</span>
<</widget>><<widget "rest">>
<<link "Rest">>
<<set $B.subject = $args[0]; $action = new Action("rest"); $B.target = null>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<span class="hotkey monospace right">[W]</span>
<</widget>><<widget "items">>
<<if $B.embargo > 0>>
<span class="embargo">
Nope! [<<print $B.embargo>>]
</span>
<<else>>
<<if ($args[0].en >= setup.ITEM_COST) || ($args[0].crafty && $args[0].en >= Math.round(setup.ITEM_COST/2))>>
<<link "Item">>
<<set $B.subject = $args[0]>>
<<replace "#phase">><<include "items">><</replace>>
<</link>>
<<else>>
<b>Item</b>
<</if>>
<</if>>
<span class="hotkey monospace right">[E]</span>
<</widget>><<widget "move">>
<<set $B.phase = "move"; _s = undefined>>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<<replace "#phase">><<include "movement phase">><</replace>>
<</widget>><<widget "spare">>
<<link "Spare">>
<<set $B.subject = $args[0]; $B.target = $enemy_to_spare; $action = {name: "spare", cost: 0}>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<</widget>><<widget "actionlist">>
/* Widget for display of player actions. */
/* Individual action widgets take cost, damage multiplier, and other relevant variables as arguments so they can be displayed in the description. Values are given in "Database: Actions". */
<<run console.assert($args.length > 0 && ($args[0] instanceof Puppet),"ERROR in actionlist: no Puppet")>>
<<set _char = $args[0]>>
<<if $args.includes("crisis")>>
<<set _actions = _char.crisis>>
<<else>>
<<set _actions = _char.actions>>
<</if>>
<<for _action range _actions>>
<<if $inbattle && !_action.passive>> /* do not display anything if a passive action and not inbattle */
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "actionDisplay">>
<<else>>
<<set _actionClass = "actionDisplay uncompressed">>
<</if>>
<div @class="_actionClass">
<<capture _action>>
<<mouseover>>
<span class="actionName">
<<if actionStandardCheck(_action)>>
/* If insufficient energy, uses, or cooldown, just display the action name (no link). */
_action.name<<if (def _action.cd && _action.cd !== 0)>> <span class="cooldowndisplay">[CD <<print _action.cd>>]</span><</if>>
<<elseif actionLockCheck(_action)>>
/* If dizzy and action not basic, display the name as crossed out followed by a gray "Dizzy!" message. */
<del>_action.name</del> <span class="dizzy">Dizzy!</span>
<<elseif actionHPCheck(_action)>>
/* If puppet has insufficient HP for an HP-consuming skill, the name is crossed out. */
<del>_action.name</del> <span class="dizzy">Not enough HP!</span>
<<elseif actionElementCheck(_action)>>
<del>_action.name</del> <span class="dizzy">No element.</span>
<<else>>
<<link "_action.name">>
<<actionLink>>
<</link>>
<</if>>
</span>
<<onmouseover>>
<<if $COMPRESSED_ACTIONS === true>>
<<run $("#actionInfo").css("visibility","visible")>>
<<replace '#actionInfo'>>
<<actionInfo _action _char "full">>
<</replace>>
<</if>>
<<onmouseout>>
<<if $COMPRESSED_ACTIONS === true>>
<<run $("#actionInfo").css("visibility","hidden")>>
<<replace '#actionInfo'>>
<<include "action box default">>
<</replace>>
<</if>>
<</mouseover>>
<</capture>>
<<if $COMPRESSED_ACTIONS === true && $inbattle>>
<<actionInfo _action _char "no name">>
<<else>>
<<actionInfo _action _char "no name" "full">>
<</if>>
</div>
<<elseif !$inbattle>> /* If outside of battle (e.g. in menu), display full action info with no interactivity. */
<div class="actionDisplay uncompressed">
<<actionInfo _action _char "full">>
</div>
<</if>>
<</for>>
<</widget>><<widget "actionInfo">>
<<run console.assert($args.length > 0 && ($args[0] instanceof Action),"ERROR in actionInfo: no arguments passed")>>
<<if $args[1] instanceof Puppet>>
<<set _char = $args[1]>>
<</if>>
<<set _act = $args[0]>>
<<if !$args.includes("no name")>><b><<print _act.name>></b><</if>>
<<if def _act.uses>> <span class="usedisplay">(Uses: <<print _act.uses>>/<<print _act.maxUses>>)</span><</if>>
<span style="float:right; margin-left:0.5em;">
<<if $args.includes("full")>>
<<if !$inbattle && _char.defaultAction.name === _act.name>>
<b>[Default]</b>
<</if>>
<<if _act.crisis>>
<b>[Crisis]</b>
<</if>>
<<if _act.basic>>
[Basic]
<</if>>
<<if _act.instant>>
[Instant]
<</if>>
<<if _act.passive>>
[Passive]
<</if>>
<</if>>
<<if _act instanceof ItemAction && !_act.crisis>>
x<<print inv().get(_act.name).stock>>
<<else>>
<<if !_act.passive && Number.isInteger(_act.cost) && ((!_act.crisis && _act.cost >= 0) || (_act.crisis && _act > 0))>>
<<print _act.cost>><<if _act.phase === "spell phase">>+<</if>> EN
<</if>>
<</if>>
</span>
<<if !$inbattle && !_act.noDefault && _char.defaultAction.name !== _act.name>>
<div style="font-size:9pt">
<<capture _act>>
<<link "[Set as default]">>
<<set _char.defaultAction = _act>>
<<replace "#menuActionList">><<actionlist _display>><</replace>>
<</link>>
<</capture>>
</div>
<</if>>
<<if $args.includes("full")>>
<<if _act instanceof ItemAction>>
<<set _act = new Item(_act.name)>>
<</if>>
<div><<print _act.info>></div>
<<if _act.desc !== null>><div class="actdesc"><<print _act.desc>></div><</if>>
<</if>>
<</widget>><<widget "actionLink">>
<<if $args.length > 0>>
<<set _action = $args[0]>>
<</if>>
<<if !(actionCheck(_action))>>
<<unset _s>>
<<if _action.passagejump>>
<<goto _action.passagejump>>
<<else>>
<<set $action = clone(_action)>>
<<if !$action.nosave>>
<<set $B.subject.lastAction = $action>>
<</if>>
<<set $B.targeting = _action.target>>
<<set $B.noself = _action.noself>>
<<if _action.phase is "confirm phase">>
<<set $B.target = null>>
<<set $B.targeting = null>>
<<elseif _action.phase is "spell phase">>
<<set $B.mincost = _action.cost>>
<<set $B.targeting = null>>
<</if>>
<<replace "#actorlist">><<include "actorlist">><</replace>>
<<replace "#phase">><<include _action.phase>><</replace>>
<</if>>
<</if>>
<</widget>><<widget "crisisLink">>
<<if typeof($args[0]) == "string">>
<<set _text = $args[0]>>
<<else>>
<<set _text = "Crisis">>
<</if>>
<span id="crisisLink">
<<link _text>>
<<replace "#phase">><<include "crisis actions">><</replace>>
<</link>>
</span>
<</widget>><<widget "itemlist">>
<<run console.assert($args.length > 0 && ($args[0] instanceof Puppet),"ERROR in actionlist: no Puppet")>>
<<set _char = $args[0]>>
<<for _k, _v range inv()>>
<<if _v.usable.includes("inbattle")>>
<div class="actionDisplay">
<<capture _v>>
<<mouseover>>
<span class="actionName">
<b>
<<if _v.stock > 0 && $inbattle>>
<<link _k>>
<<actionLink _v.action>>
<</link>>
<<else>>
<<print _k>>
<</if>>
</b>
</span>
<<onmouseover>>
<<if $COMPRESSED_ACTIONS === true>>
<<run $("#actionInfo").css("visibility","visible")>>
<<replace '#actionInfo'>>
<<actionInfo _v.action "full">>
<</replace>>
<</if>>
<<onmouseout>>
<<if $COMPRESSED_ACTIONS === true>>
<<run $("#actionInfo").css("visibility","hidden")>>
<<replace '#actionInfo'>><</replace>>
<</if>>
<</mouseover>>
<</capture>>
<<if $COMPRESSED_ACTIONS === true && $inbattle>>
<<actionInfo _v.action "no name">>
<<else>>
<<actionInfo _v.action "no name" "full">>
<br/>
<</if>>
</div>
<</if>>
<</for>>
<</widget>><<widget "newTurn">>
/* arg 0 = party array. MUST be an array; use an array with 1 element if you wish to call this for a single actor */
<<run console.assert($args[0] instanceof Array,"ERROR in newTurn: invalid argument")>>
<<for _actor range $args[0].filter(function (a) { return a !== null; })>>
<<run console.assert(_actor instanceof Actor,"ERROR in newTurn: non-Actor element")>>
<<set _actor.isDone to false>>
<<if !_actor.dead>>
<<run _actor._retaliations.refill()>>
<</if>>
<<if _actor instanceof Puppet>>
/* Puppet-exclusive tasks:
decrement respawn (enemy respawn only decremented at end of round)
regen HP (enemies regen at end of round)
refresh used actions & decrement cooldown */
<<if _actor.dead && def _actor._respawn>>
<<set _actor.respawn-->>
<<if _actor.respawn <= 0>>
<<set _actor.dead = false; _actor.hp = Math.round(_actor.maxhp * setup.RESPAWN_HP); _actor.resetRespawn()>>
<<set _message to true>>
<div id="actFlavor">
<<print _actor.respawnMessage>>
</div>
<br/>
<</if>>
<</if>>
<<if !_actor.dead>>
<<run _actor.regenHP()>>
<</if>>
<<for _action range _actor.actions>>
<<if _action.used === true>>
<<run _action.used = false>>
<</if>>
<<if _action.cd>>
<<run _action.cd -= 1>>
<</if>>
<</for>>
<</if>>
<<decayMessage _actor.effects true>>
<</for>>
<<include "custom newTurn">>
<</widget>><<widget "endOfRound">>
<<run console.assert($args[0] instanceof Array,"ERROR in endOfRound: invalid argument")>>
<<for _actor range $args[0].filter(function (a) { return a !== null; })>>
<<run console.assert(_actor instanceof Actor,"ERROR in endOfRound: non-Actor element")>>
<<set _actor.isDone = false>>
<<for _effect range _actor.effects>>
/* DoT check */
<<if _effect.dot && !_actor.dead>>
<<set _message to true>>
<<set $dmg = _effect.damage(_actor)>>
<div id="actFlavor">
<<print _effect.msg(_actor)>>
</div>
<div id="actEffect">
<<echoDamage _actor "indirect" "nocalc">>
</div>
<br/>
<</if>>
<</for>>
/* decrementor */
<<decayMessage _actor.effects false>>
<<if _actor instanceof Enemy>>
<<if !_actor.dead>>
<<run _actor.regenHP()>>
<</if>>
<<run _actor._noAttacks.refill()>>
<<if def _actor._respawn && _actor.dead>>
<<set _actor.respawn-->>
<<if _actor.respawn <= 0>>
<<set _actor.dead = false; _actor.hp = _actor.maxhp; _actor.resetRespawn()>>
<<set _message = true>>
<div id="actFlavor">
<<print _actor.respawnMessage>>
</div>
<br/>
<</if>>
<</if>>
<</if>>
<</for>>
<</widget>><<widget "decayMessage">>
/* arg 0 = effects array, arg 1 = Boolean, top decrement (optional, defaults to false) */
<<run console.assert($args[0] instanceof Array,"ERROR in decayMessage: invalid argument")>>
<<run console.assert(_actor !== undefined,"ERROR in decayMessage: undefined _actor")>>
<<set _topDec = Boolean($args[1])>>
<<set _decayMsg = "">>
<<for _effect range $args[0].filter(function (eff) { return eff.topDec == temporary().topDec })>>
<<run console.assert(_effect instanceof Effect,"ERROR in decayMessage: non-Effect element")>>
<<set _m = _effect.decay(_actor)>>
<<if _m.length > 1>>
<<set _decayMsg += _m>>
<</if>>
<</for>>
<<if _decayMsg.length > 0>>
<div id="actEffect">
<<print _decayMsg>>
</div>
<br/>
<</if>>
<</widget>><<widget "endofbattle">>
<<set $inbattle = false>>
<<for _id range $B.playerBars>>
<<run Meter.del(_id)>>
<</for>>
<<for _id range $B.enemyBars>>
<<run Meter.del(_id)>>
<</for>>
<<if deadCount() == puppets().length>>
<<set $B.defeated = true>>
<<elseif deadCount() == puppets().length-1>>
<<set $B.SoleSurvivor = true>>
<<elseif deadCount() == 0>>
<<set $B.Perfect = true>>
<</if>>
<<if !$B.defeated>>
<<set $encounters[$scenario] = true>>
<</if>>
<<if $B.style>>
<<addclass "body" $B.style>>
<<removeclass "html" $B.style>>
<</if>>
<<refreshPuppets>>
<<include "custom end of battle">>
<<unset $B; $enemies>>
<</widget>><<set $inbattle to true>>
<<set $B = {target: null, subject: null, actor: null, turn: "player", turnCounter: 0, enemyTurns: 0, actionsThisTurn: {}, phase: "selection", embargo: 0, event: false, surrender: false, kills: [], specialdeath: [], actionQueue: [], XPreward: 0, moneyReward: 0, destination: previous()}>>
<<callEncounter $scenario>>
<<for _enemy range enemies()>>
<<set $B.actionsThisTurn[_enemy.id] = []>>
<</for>>
<<refreshPuppets>>
<<if typeof($B.style) == 'string'>>
<<addclass "html" $B.style>>
<</if>>
<<set $B.playerBars = []>>
<<for _i, _p range $puppets>>
<<set _id = 'p'+_i>>
<<run $B.playerBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation setup.HEALTH_BAR_TIME linear>>
<<sizing 100%>>
<</newmeter>>
<</for>>
<<set $B.enemyBars = []>>
<<for _i, _e range $enemies>>
<<set _id = 'e'+_i>>
<<run $B.enemyBars.push(_id)>>
<<newmeter _id 1>>
<<colors setup.ENEMY_BAR_COLOR>>
<<animation setup.HEALTH_BAR_TIME linear>>
<<sizing 100%>>
<</newmeter>>
<</for>>
<<include "custom battle preparation">>
<<set $stScreen = 1>>
<<if $B.ambush>>
<<set $B.turn = "enemy">>
<<goto "enemy phase">>
<<else>>
<<goto "Battle!">>
<</if>><<specialcheck>>
<<victorycheck>>
<<if $B.specialdeath.length > 0>>
<<set _specialmsg = true>>
<<goto `$B.specialdeath[0]`>>
<<run $B.specialdeath.shift()>>
<</if>>
<<set $B.phase = "selection">>
<<set _doneCount = 0>>
<<for _puppet range puppets()>>
<<if (_puppet.isDone || _puppet.dead || _puppet.noact) && !_puppet.down>>
<<set _doneCount++>>
<</if>>
<</for>>
<<if !($B.victory || $B.defeat || _specialmsg)>>
<<if $AUTO_ENDTURN === true && _doneCount == puppets().length>>
<<endturn>>
<<elseif setup.TURN_EXCHANGE === true && $B.enemyTurns > 0>>
<<set $B.phase = null>>
<<goto "enemy phase">>
<</if>>
<</if>>
<span id="status">
<<include status>>
</span>
<span id="content">
<span id="actorlist">
<<include "actorlist">>
</span>
<div id="phase">
</div>
</span>/* setup */
<<set _queue = new Set()>>
<<set _enemiesClass = "actors enemies">>
<<set _puppetsClass = "actors">>
<<include "battle display mods actorlist">>
<<if setup.BATTLE_GRID === true>>
<<set _enemiesClass += " grid"; _puppetsClass += " grid">>
<</if>>
<div @class="_enemiesClass" id="enemies">
<<include "actorlist enemies">>
</div>
<div id="battlelines">
<<include "special battle lines">>
</div>
<div id="puppets">
<<include "actorlist puppets">>
</div><<set _martyr = enemies().find(function (e) { return e.martyr})>> /* check if an enemy martyr exists; used in targeting */
<<for _i, _enemy range $enemies>>
<<set _enemyClass = "enemy">>
<<if setup.BATTLE_GRID === true>>
<<set _enemyClass += " grid">>
<<if _enemy !== null && _enemy.large>>
<<set _enemyClass += " large">>
<</if>>
<<elseif _enemy.large>>
<<set _enemyClass += " full">>
<</if>>
<<if _enemy !== null && !_enemy.hidden>>
<<actorBox _enemy _enemyClass>>
<<elseif _enemy === null>>
<div @class="'actor '+_enemyClass">
/* no content; empty box */
</div>
<<else>>
/* display nothing */
<</if>> /* end null if */
<</for>>
<<if setup.BATTLE_GRID === true>>
<<timed 0s>>
<<script>>
$("#enemies.actors.grid").css({
"grid-template-columns": `repeat(${setup.ROW_SIZE},1fr)`
});
<</script>>
<<run reverseChildren(document.getElementById("enemies"))>>
<</timed>>
<</if>><div @class="_puppetsClass">
<<if setup.BATTLE_GRID === true && $B.phase == "move">>
<div style="position:absolute; top:0; right:0"><<backbtn>></div>
<</if>>
<<if def _s>>
<div style="position:absolute; top:0; right:0"><<backbtn>></div>
<<actorBox $B.subject>>
<<else>>
<<if setup.BATTLE_GRID === true>>
<<if $B.phase == "selection">>
<div id="endturn" style="position:absolute; top:0; right:0">
<<include "End Turn Button">>
</div>
<</if>>
<</if>>
<<for _i, _puppet range $puppets>>
<<set _puppetClass = "">>
<<if setup.BATTLE_GRID === true>>
<<set _puppetClass += " grid">>
<</if>>
<<if _puppet === null>>
<div @class="'actor '+_puppetClass">
<<if $B.phase == "move">>
<div style="display: flex; justify-content: center; align-items: center; height: -webkit-fill-available">
<<link "[MOVE]">>
<<set _m = $puppets.indexOf($B.subject)>>
<<set $puppets[_i] = $B.subject; $puppets[_m] = _puppet>>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<</link>>
</div>
<<else>>
/* no content; empty box */
<</if>>
<<else>>
<<actorBox _puppet _puppetClass>>
<</if>>
<</for>>
<br/>
<</if>>
</div>
<<if setup.BATTLE_GRID != true && $B.phase == "selection">>
<br/>
<div id="endturn">
<center>
<<include "End Turn Button">>
</center>
</div>
<br/>
<br/>
<span id="quit"><<include "Quit Button">></span>
<div style="font-weight:bold; float:right">ROUND <<print $B.turnCounter>></div>
<</if>>
<<if setup.BATTLE_GRID === true>>
<<timed 0s>>
<<script>>
$("#puppets.actors.grid").css({
"grid-template-columns": `repeat(${setup.ROW_SIZE},1fr)`
});
<</script>>
<</timed>>
<</if>><<if $B.enemyTurns == 0 || _doneCount == puppets().length>>
<<button "END TURN">>
<<endturn>>
<</button>>
<<else>>
<<button "ADVANCE TURN">>
<<set $B.phase = null>>
<<goto "enemy phase">>
<</button>>
<</if>><<link "Surrender and return to the Hub">><<replace "#quit">>Really quit? [[Yes|Defeat]] | <<link "No">><<replace "#quit">><<include "Quit Button">><</replace>><</link>><</replace>><</link>><div class="commandcontainer">
<div class="commands">
<span id="actbtn"><<act $puppets[_s]>></span><br />
<span id="restbtn"><<rest $puppets[_s]>></span><br />
<span id="itembtn"><<items $puppets[_s]>></span><br />
<<if setup.BATTLE_GRID === true>><<move $puppets[_s]>></span><br /><</if>>
<<if $B.surrender is true>>
<<spare $puppets[_s]>><br/>
<</if>>
</div>
</div><<if def $B.actor && $B.actor !== null>>
<div class="statusname">$B.actor.name</div>
<div style="float: right;">
<<if def setup.STATUS_SCREENS.battle && setup.STATUS_SCREENS.battle.length > 0>>
<span id="statusback">
<<link "<">>
<<set $stScreen-->>
<<if $stScreen < 1>>
<<set $stScreen = setup.STATUS_SCREENS.battle.length+1>>
<</if>>
<<if setup.STATUS_SCREENS.battle[$stScreen-2] == "aggression" && $B.actor instanceof Puppet>>
<<set $stScreen-->>
<</if>>
<<replace "#status">><<include "status">><</replace>>
<</link>>
</span>
<span id="statusforward">
<<link ">">>
<<set $stScreen++>>
<<if setup.STATUS_SCREENS.battle[$stScreen-2] == "aggression" && $B.actor instanceof Puppet>>
<<set $stScreen++>>
<</if>>
<<if $stScreen > setup.STATUS_SCREENS.battle.length+1>>
<<set $stScreen = 1>>
<</if>>
<<replace "#status">><<include "status">><</replace>>
<</link>>
</span>
<</if>>
</div>
<br/>
<<if $stScreen == 1>>
/* 1 = stat screen, default, unlinked to STATUS_SCREENS and always available */
<<for _k, _v range $B.actor.stats>>
<<if !setup.hiddenStats.includes(_k)>>
<span class="statname"><<print _k>>:</span>
<span class="stat">
<<if $B.actor.maskstats || ($B.actor instanceof Enemy && def $bestiary && !$bestiary.fetch($B.actor.name).statsKnown[_k])>>
<b>??</b>
<<else>>
<<stat _k>>
<</if>>
</span>
<br/>
<</if>>
<</for>>
<<elseif setup.STATUS_SCREENS.battle[$stScreen-2] == "elements">>
<div style="font-size: 12px;">ELEMENTAL RESISTANCE</div><br/>
<<for _k, _v range $B.actor.elements>>
<span class="statname"><<print _k>></span>
<span style="float:right">
<<if def $bestiary && $B.actor instanceof Enemy && !$bestiary.fetch($B.actor.name).statsKnown[_k]>>
<<if setup.SOAK>>?? /<</if>><div style="display: inline-block; min-width:55px; text-align:right">???%</div>
<<else>>
<<if setup.SOAK>><<soak _v>> /<</if>><div style="display: inline-block; min-width:55px; text-align:right"><<res _v>></div>
<</if>>
</span>
<br/>
<</for>>
<<elseif setup.STATUS_SCREENS.battle[$stScreen-2] == "ailments">>
<div style="font-size: 12px;">AILMENT TOLERANCE</div><br/>
<<for _k, _v range $B.actor.tolerances>>
<<if _v.current != 0>>
<<if def $bestiary && $B.actor instanceof Enemy && !$bestiary.fetch($B.actor.name).tolerancesKnown[_k]>>
<span class="tolerance">????</span>
<<else>>
<<print _k>>
<<tol _v>>
<</if>>
<br/>
<</if>>
<</for>>
<<elseif setup.STATUS_SCREENS.battle[$stScreen-2] == "aggression">>
<div style="font-size: 12px;">AGGRESSION</div><br/>
<<set _totalThreat = 0>>
<<for _k, _v range $B.actor.threat>>
<<set _totalThreat += _v>>
<</for>>
<<if _totalThreat == 0>>
<<set _totalThreat = 1>>
<</if>>
<<for _k, _v range $B.actor.threat>>
<span class="statname"><<print _k>></span>
<<set _value = (_v / _totalThreat)*100; _value = _value.toFixed(0)>>
<span class="right"><<print _value+"%">></span>
<br/>
<</for>>
<</if>>
<br/>
<<effectinfo>>
<</if>><<set $B.phase = "actions">>
<span class="hotkey monospace">
[Q] = basic action
<<if subject().lastAction instanceof Action>> |
<<if actionCheck(subject().lastAction)>>
<<set _style = "color:gray">>
<<else>>
<<set _style = "">>
<</if>>
<span @style=_style> [W] = last action (<<print subject().lastAction.name>>)</span>
<</if>>
<<if subject().crisis instanceof Array && subject().crisis.length > 0>> |
<<if subject().crisisPoints >= 100>>
<<set _style = "font-weight:bold">>
<<else>>
<<set _style = "color:gray">>
<</if>>
<span @style=_style> [E] = <<crisisLink>></span>
<</if>>
</span><br/><br/>
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "compressed">>
<div id="actionInfo">
</div>
<<else>>
<<set _actionClass = "">>
<</if>>
<div id="actionlist" @class="_actionClass">
<<actionlist $B.subject>>
</div><<set $B.phase = "crisis">>
<span class="hotkey monospace">
[E] = <span id="regularActions">
<<link "Regular Actions">>
<<replace "#phase">><<include "actions">><</replace>>
<</link>></span>
</span><br/><br/>
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "compressed">>
<div id="actionInfo">
</div>
<<else>>
<<set _actionClass = "">>
<</if>>
<div id="actionlist" @class="_actionClass">
<<actionlist $B.subject "crisis">>
</div><<set $B.phase = "items">>
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "compressed">>
<div id="actionInfo">
</div>
<<else>>
<<set _actionClass = "">>
<</if>>
<div id="actionlist" @class="_actionClass">
<<itemlist>>
</div><div class="actionBoxHelp">Hover over an action to see info</div><<set $B.phase = "targeting">>
<<replace "#actorlist">><<include "actorlist">><</replace>>
<<if $B.targeting == "enemy">>
<<addclass "#puppets" "invisible">>
<<elseif $B.targeting == "ally">>
<<addclass "#enemies" "invisible">>
<</if>>
<<backbtn>>
Select a target. <span class="hotkey monospace">[hotkeys: 1-0] <<if $B.reverse_display>>[enemies are displayed in reverse order]<</if>></span><br/>
<br/>
<<if $B.targeting == "all">>
<<set _targetingEnemy = true>>
<span class="hotkey monospace" id="target_help">
[Hotkeys targeting <<if _targetingEnemy === true>>enemies<<else>>allies<</if>>. Press Shift to switch targets.]
</span>
<</if>><<replace "#actorlist">><</replace>>
<<if $SHOW_CONFIRM>>
<<set $B.phase = "confirm">>
<<backbtn>>
$B.subject.name will
<<if $action.name is "rest">>
<b>rest</b> this turn.
<<elseif $action.name is "struggle">>
spend <<print setup.STRUGGLE_COST>> Energy to get back on <<print subject().their>> feet.
<<elseif $action.item is true>>
use <<switch $action.name.first().toLowerCase()>><<case 'a' 'e' 'i' 'o' 'u'>>an<<default>>a<</switch>> $action.name.
<<elseif $action.name is "spare">>
accept the enemy's surrender.
<<else>>
use <b>$action.name</b><<if $B.target isnot null>> on <b>$B.target.name</b><</if>>.
<</if>>
<span id="confirmLink">[[Confirm?|action phase]]</span><br />
<span class="preview">
<<if $action.preview instanceof Function>>
<<print $action.preview()>>
<<else>>
<<print $action.preview>>
<</if>>
</span>
<<else>>
<<goto "action phase">>
<</if>><center id="continue">
<<if passage() == "Anim Test Live">>
<<button "Return to Landing" "Start">><</button>>
<<else>>
<<button "Continue...">>
<<if $B.actionQueue.length > 0>>
<<replace "#content" "t8n">>
<<include "Action Queue">>
<<timed 0s>><<include "animation activator">><</timed>>
<</replace>>
<<elseif passage() == "enemy phase" && ($B.enemyTurns > 0 || $B.turn == "enemy")>>
/* As long as there are enemy turns remaining (or the enemy's turn is active), continue refreshing the enemy phase until the enemies are all done. */
<<goto "enemy phase">>
<<else>>
<<goto "Battle!">>
<</if>>
<</button>>
<</if>>
</center><<set $B.phase = null>>
<span id="status">
<<include "status">>
</span>
<span id="content">
/* Some abilities (such as AoE attacks) don't always end with the same target as the one they started with. This saves the initial target if you want to use it for something, e.g. a reaction scene. */
<<set _initialTarget = $B.target>>
/* Saves the number of kills from before the action. By comparing this to the kills array afterwards, you can identify whether or not a kill happened during the action. */
<<set _initialKills = $B.kills.length>>
/* Tracks characters who have counterattacked this action. Required to enable counters. */
<<set _counters = []>>
<<if $action.name == "spare">>
<<run $enemy_to_spare.surrender()>>
<</if>>
/* Add any bonus threat from the action. */
<<if setup.THREAT_TARGETING === true && target() instanceof Enemy && subject() instanceof Puppet>>
<<run target().threat.inc(subject().name,$action.threat)>>
<</if>>
<<include "action effects">>
/* Variable cleanup. Due to the way goto works, this will work even for silent actions. */
/* Remember this action for the last action shortcut: */
<<if !$action.nosave && !$action.passagejump && !($action.name == "struggle" || $action.name == "spare")>>
<<set $B.subject.lastAction = $B.subject.actions.find(function (a) { return a && a.name == $action.saveMod })>>
<</if>>
/* If action has limited uses, decrement that */
<<if def $action.uses>>
<<run subject().actions.find(function(a) { return a && a.name == $action.name }).uses -= 1>>
/* Have to perform a search because $action is a clone of the original, reducing its uses will not reduce the uses of the action in the subject's actions list */
<</if>>
/* If action has a cooldown, reset it */
<<if def $action.cd>>
<<run subject().actions.find(function(a) { return a && a.name == $action.name }).resetCD()>>
/* Have to perform a search because $action is a clone of the original */
<</if>>
/* If target was an enemy (i.e. an attacking skill was used), subject is marked as attacker. (This is for enemies that target the last puppet to attack them.) */
<<if $B.target instanceof Enemy>>
<<set $B.attacker = $puppets.indexOf($B.subject)>>
<</if>>
/* isDone logic; checks for confounding factors */
<<if $action.instant>>
/* do nothing */
<<elseif $B.subject.inspired>>
<<set $B.subject.inspired = false>>
<<else>>
<<set $B.subject.isDone = true>>
<<set $B.enemyTurns++>>
<</if>>
<<if $action.oncePerTurn>>
<<run subject().actions.find(function(a) { return a && a.name == $action.name }).used = true>>
<</if>>
<<if $B.surrender && def $action.name>>
<<print $enemy_to_spare.surrenderCheck()>>
<</if>>
<br/>
<<include "Battle Continue Button">>
</span>/* Where the action actually happens. Kept separate from action phase for modularity purposes. */
/* Subtract action cost */
<<if typeof(subject().en) == "number">>
<<if $action instanceof ItemAction>>
<<set $B.item_used = true>>
<<if $B.subject.crafty>>
<<set $action._cost = Math.round(setup.ITEM_COST/2)>>
<<else>>
<<set $action._cost = setup.ITEM_COST>>
<</if>>
<</if>>
<<set $B.subject.en -= $action.cost>>
<</if>>
<<if $action.hpcost > 0>>
<<set subject().hp -= $action.hpcost>>
<</if>>
<<if subject() instanceof Enemy && Number.isInteger($action.enemyCD)>>
<<run subject().cd.set($action.nameCD,$action.enemyCD)>>
<</if>>
<<if $action.silent === true>>
<<if $action.act instanceof Function>>
<<print $action.act()>>
<<else>>
<<print $action.act>>
<</if>>
<<goto "Battle!">>
<<else>>
<<set _queue = new Set()>> /* Queue for damage popups. */
<div style="display:flex; flex-direction:column"> /* container for action display components */
/* Flavor text relating to the action. It is a div element, making it a separate block from the gameplay effects. By default, it has a bottom margin of 1em to create a separator between flavor text and gameplay text. */
<div id="actFlavor">
/* First is action use text. A regular action takes the form "Actor uses..." Items take the form "Actor uses a/n..." This is a div element, which means it creates an automatic line break. */
<<if !($action.useText === null || ($action.useText instanceof Function && $action.useText() === null))>>
/* Set useText to null to bypass this element. This is useful if you only want one of the two areas to display. */
<div id="useText">
<<if $action.useText instanceof Function>>
<<print $action.useText()>>
<<else>>
<<print $action.useText>>
<</if>>
</div>
<</if>>
<<include "battle interruptions">>
/* Then descriptive text. This is a div element, so it creates an automatic line break. */
<<if $action.actText != null>>
/* Set actText to null to bypass this element. This is useful if you only want one of the two areas to display. */
<div id="actText">
<<if $action.actText instanceof Function>>
<<print $action.actText()>>
<<else>>
<<print $action.actText>>
<</if>>
</div>
<</if>>
</div> /* close flavor text div */
/* Then effects. */
<<if $action.act !== null>>
<div id="actEffect">
<<if def _targetingMsg>>
/* This section covers text that has to be generated elsewhere for various reasons, such as the alert for a Protector covering another character. */
<<print _targetingMsg>>
<<unset _targetingMsg>>
<</if>>
<<if $action.act instanceof Function>>
<<print $action.act()>>
<<else>>
<<print $action.act>>
<</if>>
</div> /* close action effects div */
<</if>>
<<if $ANIMATIONS === true && _queue.size > 0>>
<div class="actors animationContainer">
<<set _animationActive = true>>
<<for _i, _p range _queue>>
<div style="position:relative">
<div @id="'box'+_i" style="display:inline-block">
<<capture _p>>
<<liveblock>>
<<actorBox _p "" "simplified">>
<</liveblock>>
<</capture>>
</div>
<<for _x, _m range _p.battleMsg>>
<div @id="'dmg'+_i+'-'+_x" class="dmgPopup">
<<print _m.content>>
</div>
<</for>>
</div>
<</for>>
</div>
<</if>>
</div> /* close container */
<</if>> /* end silent check */
/* If action was a Crisis, reset crisis points */
<<if $action.crisis && def subject().crisisPoints>>
<<set subject().crisisPoints = 0>>
<</if>>
/* If action was an item, decrement item stock and reset item flag */
<<if subject() instanceof Puppet && $action instanceof ItemAction && !$action.free>>
<<print $inventory.decItem($action.name)>>
<</if>>
/* Set last element for e.g. Artist's attacks */
<<if typeof($action.element) == 'string'>>
<<set subject().lastUsed = $action.element>>
<</if>>
<<include "custom end of action effects">>
<<if def _OG>>
<<set $B.subject = _OG.subject; $B.target = _OG.target; $action = _OG.action>>
<<unset _OG>>
<</if>><<set $B.phase = "spell">>
<<backbtn>>
<<if isNaN($action.cost)>>
You need to input a number.<br/>
<<elseif $action.cost < $B.mincost>>
Spell requires at least $B.mincost Energy.<br/>
<<elseif $B.subject.en < $action.cost>>
Not enough Energy!<br/>
<</if>>
How much Energy do you want to put into $action.name? (Minimum $B.mincost)<br />
<<numberboxplus "$action._cost" $B.mincost autofocus>>
<<include "spell check">>
<</numberboxplus>><br/>
(Press Enter to confirm.)<br/><<run $action._cost *= 1>>
<<if ($action.cost < $B.mincost) or ($B.subject.en < $action.cost) or isNaN($action.cost)>>
<<replace "#phase">><<include "spell phase">><</replace>>
<<else>>
<<run $action.spellMod()>>
<<if $action.phase != "confirm phase">>
<<set $B.targeting = $action.target>>
<<replace "#phase">><<include "targeting phase">><</replace>>
<<else>>
<<replace "#phase">><<include $action.phase>><</replace>>
<</if>>
<</if>><span id="status">
<<include status>>
</span>
<span id="content">
<<if $B.turn == "player">>
/* If the turn reads "player", it's because the enemy round just finished. Run end of turn for enemies. */
/* If it's a new player turn, it's a "true" new round, so need to update and reset controller variables. */
<<set $B.turnCounter++; $B.embargo--; $B.enemyTurns = 0>>
<<endOfRound $enemies>>
<<newTurn $puppets>>
<<if _message>>
<<button "Continue..." "Battle!">><</button>>
<<else>>
<<goto "Battle!">>
<</if>>
<<elseif $B.turn eq "enemy">>
/* If the turn reads "enemy", the player turn just finished. Run end of turn for player. */
<<endOfRound $puppets>>
<<if _message>>
<br/><<button "Continue..." "enemy phase">><</button>>
<<else>>
<<goto "enemy phase">>
<</if>>
<</if>>
</span><<victorycheck>>
<span id="status">
<<include status>>
</span>
<span id="content">
<<if (deadCount() == puppets().length)>>
/* If all puppets are dead, it's game over -- no point in finishing this passage, just let victorycheck do its thing. */
<<else>>
<<if $B.ambush>>
<<set $B.ambush = false; $B.turn = "enemy">>
<center style="font-weight:bold">AMBUSHED!</center><br/>
<</if>>
<<set _start = $B.enemyTurns>>
<<set _enemy = enemies().sort(function(a,b) { return a.priority - b.priority; })
.find(function (e) { return e && !e.isDone && !e.dead })>>
/* Returns the first active enemy that is not dead */
<<if _enemy instanceof Enemy>>
/* If first check fails, could not find a valid enemy -- all enemies have acted */
<<set $B.subject = _enemy>>
<<set $B.target = null>>
<<if _enemy.isFirstAction>>
<<newTurn `[_enemy]`>>
<</if>>
<<set _enemy.isDone = true>>
<<if !_enemy.fakedeath>>
<<if _enemy.noact>>
<<set _effect = _enemy.effects.sort(function(a,b) { return a.priority - b.priority; })
.find(function (e) { return e && e.holdAction instanceof Function })>>
/* Sorts effects by priority and returns the first hold effect (one with a holdAction) */
<<run console.assert(_effect !== undefined,`ERROR in enemy phase: ${_enemy.name} has noact but no hold effect`)>>
<<set $action = _effect.holdAction()>>
<<include "action effects">><br/>
<<else>>
<<if _enemy.isFirstAction>>
<<run _enemy.decCD()>>
<</if>>
<<set _counters = []>>
<<set $action = null>>
<<run _enemy.actions()>>
<<run $B.actionsThisTurn[_enemy.id].push($action.name)>>
<<if !_targetfail>>
<<include "action effects">><br/>
<</if>>
<<if $action.fullround === true>>
/* Full-round actions use up all remaining attacks. */
<<run _enemy.noAttacks = 0>>
<<elseif !$action.instant>>
/* Otherwise, noAttacks is reduced by 1, unless the action was instant. */
<<run _enemy.noAttacks-->>
<</if>>
<<if _enemy.noAttacks > 0>>
/* If the enemy still has attacks remaining, their turn isn't done; set isDone to false so they will act again when this passage is re-called. */
<<set _enemy.isDone = false>>
<<else>>
/* Otherwise, the enemy is done; decrement their turn from the pool. */
<<run $B.enemyTurns -= 1>>
<</if>>
<</if>>
<<else>>
/* If faking death, enemies should do nothing, so just go to the next enemy.
All code outside this if such as newTurn and threat decay will still execute. */
<<goto "enemy phase">>
<</if>> /* end fakedeath if */
<<run _enemy.decayThreat()>>
<<else>>
/* If this check failed, the enemy turn is done. Switch to player turn and run end of round. */
<<set $B.turn = "player">>
<<goto "end of round">>
<</if>>
<<include "Battle Continue Button">>
<</if>>
</span><<widget "damageCalc">>
<<set _w = $action.weight>>
<<if $args[0] instanceof Actor>>
<<set _target = $args[0]>>
<<else>>
<<set _target = $B.target>>
<</if>>
<<set _atk = ($B.subject.get("Attack") * (1 - $action.useSpecial)) + ($B.subject.get("Special") * $action.useSpecial)>>
/* Piercing? */
<<if $action.pierce>>
<<set _def = Math.min(_target.get("Defense"),setup.MIN_STAT)>>
<<else>>
<<set _def = _target.get("Defense")>>
<</if>>
/* base damage */
<<include "damageCalc formula">>
/* Find element factor, and multiply it to damage */
<<set _factor = 1; _flatFactor = 0>>
<<if typeof($action.element) == "string">>
<<set _factor = _target.getElement($action.element,"percent")>>
<<set _flatFactor = _target.getElement($action.element,"flat")>>
<<elseif $action.element instanceof Array>>
<<set _factor = 0>>
<<if setup.AVERAGE_ELEMENTS === true>>
<<for _v range $action.element>>
<<set _factor += _target.getElement(_v,"percent")>>
<<set _flatFactor += _target.getElement(_v,"flat")>>
<</for>>
<<set _factor = _factor*1.0/$action.element.length>>
<<set _flatFactor = _flatFactor*1.0/$action.element.length>>
<<else>>
<<for _v range $action.element>>
<<set _factor = Math.max(_target.getElement(_v,"percent"),_factor)>>
<<set _flatFactor = Math.min(_target.getElement(_v,"flat"),_flatFactor)>>
<</for>>
<</if>>
<</if>>
<<set _baseDmg = $dmg>>
<<set $dmg *= _factor; $dmg -= _flatFactor>>
/* Element message */
<<if def setup.elementMessages>>
<<if _baseDmg < $dmg>>
<<set _elntmsg = setup.elementMessages.weakpoint>>
<<elseif _baseDmg > $dmg && $dmg > 0>>
<<set _elntmsg = setup.elementMessages.resist>>
<<elseif $dmg < _baseDmg && $dmg == 0>>
<<set _elntmsg = setup.elementMessages.immune>>
<<elseif $dmg < _baseDmg && $dmg < 0>>
<<set _elntmsg = setup.elementMessages.absorb>>
<</if>>
<</if>>
<<include "damageCalc custom factors">>
<<if !$B.phase && $dmg > 0>>
/* Only check for critical hits if:
-no battle phase is specified (we are not selecting actions)
-damage was not absorbed
*/
<<critCheck>>
<</if>>
/* Check against minimum */
<<if ($dmg < _baseDmg && $dmg <= 0)>>
/* If elemental factors pushed damage to 0 or below, we shouldn't bump it up to the minimum; do nothing */
<<elseif _noDmgFloor && $dmg < 0>>
<<set $dmg = 0>>
<<set _noDmgFloor = false>> /* preventing data bleed */
<<elseif $dmg < setup.MIN_DMG>>
<<set $dmg = setup.MIN_DMG>>
<</if>>
/* Round to integer */
<<set $dmg = Math.round($dmg)>>
<</widget>><<widget "echoDamage">>
<<if $args.length > 0 && $args[0] instanceof Actor>>
<<set $B.target = $args[0]>>
<</if>>
<<run console.assert($B.target instanceof Actor,"ERROR in echoDamage: invalid target")>>
<<set _dmgMods = $args>>
<<set _hit = true>>
/* Protection check */
<<if !(_dmgMods.includes("indirect") || target().dead) && subject() instanceof Puppet && target() instanceof Enemy && target().protectedBy>>
<<set _temp = $B.target.name>>
<<set $B.target = $enemies.find(function(t) { return t && t.id === $B.target.protectedBy; })>>
<<print $B.target.name+" took the hit for "+_temp+"!\n">>
<</if>>
<<if _queue instanceof Set>>
<<set _queue.add($B.target)>>
<</if>>
<<if _dmgMods.includes("indirect")>>
<<set _hit = true>>
<<else>>
<<accuracyCheck>>
<</if>>
<<if _hit === false>>
<<print setup.MISS_MESSAGE>>
<<set target().battleMsg.push({shake: false, content:"MISS"})>>
<<elseif _hit === true>>
<<if def $bestiary && target() instanceof Enemy && $action.element>>
<<set $bestiary.fetch(target().name).statsKnown[$action.element] = true>>
<</if>>
<<if !(_dmgMods.includes("nocalc") || _dmgMods.includes("indirect"))>>
<<damageCalc>>
<</if>>
<<if !target().dead>>
<<set _continue = true>>
<<if !(_dmgMods.includes("unblockable") || _dmgMods.includes("indirect"))>>
<<shieldCheck>>
<</if>>
<<if _continue>>
<<if def _elntmsg>><b><<print _elntmsg+" ">></b><</if>>
<<if $dmg < 0>>
$B.target.name feeds on the energy, and recovers <<print $dmg*-1>> HP.
<<set target().battleMsg.push({shake: false, type: "healing", content:$dmg*-1})>>
<<else>>
$B.target.name takes $dmg damage!
<<if !(_dmgMods.includes("indirect") || !(_queue instanceof Set) || $ANIMATIONS === false)>>
<<set target().battleMsg.push({shake: true, type: "damage", content:$dmg})>>
<<if _elntmsg == setup.elementMessages.weakpoint && typeof(setup.popupMod.weakpoint) == "string">>
<<set target().battleMsg.last().mod = setup.popupMod.weakpoint>>
<<elseif _crit && typeof(setup.popupMod.crit) == "string">>
<<set target().battleMsg.last().mod = setup.popupMod.crit+" big">>
<<set _crit = false>>
<<elseif _elntmsg == setup.elementMessages.resist && typeof(setup.popupMod.resist) == "string">>
<<set target().battleMsg.last().mod = setup.popupMod.resist>>
<<elseif _elntmsg == setup.elementMessages.immune>>
<<set target().battleMsg.last().content = "IMMUNE";
target().battleMsg.last().type = "block">>
<</if>>
<</if>>
<<if !_dmgMods.includes("indirect")>>
<<include "crisis formula">>
<</if>>
<</if>><br />
<<if _dmgMods.includes("indirect") || !(_queue instanceof Set) || $ANIMATIONS === false>>
<<set $B.target.hp -= $dmg>>
<</if>>
<<if !_dmgMods.includes("indirect")>>
<<if setup.THREAT_TARGETING === true && subject() instanceof Puppet && target() instanceof Enemy>>
<<include "echoDamage threat gain">>
<</if>>
<<if target().dmgreflection && !_dmgreflecting && target() !== subject() && $dmg > 0>>
/* If target has defined damage reflection, they reflect the attack's damage times their damage reflection value onto their attacker. Note that this code is executed before deathcheck, meaning damage will be reflected even if the attack defeats the enemy. You can change this by moving deathcheck above this section and including a check that disallows damage reflection if the target is dead. */
<<set $dmg = Math.round($dmg * target().dmgreflection)>>
<<set _OG = {target: $B.target, subject: $B.subject}>>
<<set $B.target = _OG.subject; $B.subject = _OG.target>>
<<set _dmgreflecting = true>> /* this prevents infinite recursion if a damage reflector attacks a damage reflector; can only reflect damage once per attack */
<<echoDamage "nocalc">>
<<set $B.target = _OG.target; $B.subject = _OG.subject>>
<<unset _OG; _dmgreflecting>>
<</if>> /* end dmgreflection block */
<<if target().onHit instanceof Array && target().onHit.length > 0 && !_dmgreflecting && target().id !== subject().id>>
/* If target has onHit functions, they are executed here. Note that the target and subject are not reversed here, so if you want the effect to target the attacker, use "subject" as the selector, and vice versa. */
<<for _action range target().onHit>>
<<if _action instanceof Function>>
<<print _action()>>
<<else>>
<<run console.log("ERROR in onHit: onHit elements must be functions")>>
<</if>>
<</for>>
<</if>> /* end onHit block */
<</if>> /* end indirect block */
<<deathcheck>>
<<if !target().dead && !_dmgMods.includes("indirect")>>
<<if !$action.noShock>>
<<for _effect range target().effects.filter(function (eff) { return eff && Number.isInteger(eff.shock)})>>
<<set _shock = random(1,100)>>
<<if _shock <= _effect.shock>>
<<print target().removeEffect(_effect)>>
<</if>>
<</for>>
<</if>> /* end shock block */
<<if target().offbalance && $dmg >= 0>>
<<print target().addEffect("Knocked Down",{time: -1}>>
<</if>> /* end off-balance block */
<</if>> /* end post-deathcheck block */
<</if>> /* end shield check block */
<</if>> /* end target dead check block */
<</if>> /* end accuracy check block */
<<include "Counter Logic">>
<</widget>><<if !(_dmgMods.includes("indirect") || _dmgMods.includes("nocounter") || _counterActive || target().dead || target().noact || target().retaliations === 0 || target() === subject())
&& _counters instanceof Array
&& !_counters.includes(target().id)
&& target().counter instanceof Action>>
/* Branch for adding target character to counterattack protocol. Counterattacks are only considered if you have enabled counters by setting the _counters variable before the action; this is done at the start of the action and enemy phases in the default engine, enabling counters for both puppets and enemies. */
/* By default, counters can trigger even on missed attacks. */
/* Target must hold a valid Action in its counter attribute to counterattack, and cannot be under a hold effect or have exhausted its allowed number of retaliations for this turn. */
<<if target().counter.trigger>>
/* This check is for if you want counters to be triggered only on special circumstances, such as only if the attack is ranged, or only if the attack makes contact. Define the trigger as a function in the counter action that returns true if the counter is triggered and false otherwise. By default, an unset trigger will return true, meaning the counter will always activate. */
<<run _counters.push(target().id)>> /* Adds target to counter list, which will be read at the end of this action to perform the counter */
<<run $B.actionQueue.push([target().id,target().counter])>>
<<if target().retaliations > 0>>
/* Set retaliations to -1 for infinite counters. */
<<set target().retaliations -= 1>>
<</if>>
<</if>>
<</if>><<widget "shieldCheck">>
<<if $args[0] instanceof Actor>>
<<set _targ = $args[0]>>
<<else>>
<<set _targ = target()>>
<</if>>
<<set _shield = _targ.effects.find(function (eff) { return eff && eff.shield; })>>
<<if ndef _shield>>
<<set _continue = true>>
<<elseif _shield.onHit instanceof Function>>
<<run console.log(`echoDamage for ${target().name}: shield found`)>>
<<run _shield.uses -= 1>>
<<set _targ.battleMsg.push({shake: false, type: "block", content:"BLOCKED"})>>
<<print _shield.onHit(_targ)>>
<<set _continue = false>>
<<set _hit = false>>
<<else>>
<<run console.log("ERROR in echoDamage: shield effect has no onHit function")>>
<</if>>
<</widget>><<widget "accuracyCheck">>
<<if $args[0] instanceof Actor>>
<<set _target = $args[0]>>
<<else>>
<<set _target = $B.target>>
<</if>>
<<include "accuracy formula">>
<</widget>><<widget "critCheck">>
<<include "crit formula">>
<</widget>>/* DEPRECIATED as of v1.28. Use the Effect.calculatePower method instead. */
<<widget "effectcalc">>
<<switch $args[0]>>
<<case "debuff">>
<<set _power = Math.round((setup.effbase+setup.effdamper*(_subject.get("Special")-_target.get("Special")))*$action.effweight)>>
<<if _power < setup.min_debuff>>
<<set _power = setup.min_debuff>>
<</if>>
<<case "buff">>
<<set _power = Math.round((setup.effbase+setup.effdamper*(_subject.get("Special")))*$action.effweight)>>
<<if _power < setup.min_buff>>
<<set _power = setup.min_buff>>
<</if>>
<</switch>>
<</widget>><<longreturn>><<set _decurseCost = 1; _appraiseCost = _decurseCost>>
Run afoul of some nasty curses? Not to worry, a cleric of MegaCorp® is on staff and happy to help... for the right price, of course.
That price is <<print _decurseCost>> <<print setup.CURRENCY_NAME>>.
<b>Points: <<live $currency>></b>
<<nobr>>
<div id="business-area">
<<include "decurse options">>
</div>
<</nobr>><<if $currency < _decurseCost>>
Oh, dear. It seems you can't afford the cleric's services. Better luck next time! If there is a next time.
<<else>>
<<link "Appraise suspicious item">>
<<replace "#business-area" t8n>><<include "decurse-appraise">><</replace>>
<</link>>
<br/>
<br/>
<<link "Remove cursed item">>
<<replace "#business-area" t8n>><<include "decurse-remove">><</replace>>
<</link>>
<</if>><<set _x = Array.from($inventory.values()).filter(function (item) { return item && (item.sticky === true && !item.known); })>>
<<if _x.length > 0>>
<<link "Try a different service...">>
<<replace "#business-area" t8n>><<include "decurse options">><</replace>>
<</link>>
<br/>
<br/>
<<for _index, _item range _x>>
<div @id="_index">
<b>_item.name</b>
<div class="shop-button">
<<if $currency >= _appraiseCost>>
<<capture _item, _index>>
[<<link "APPRAISE">>
<<set $currency -= 1>>
<<update>>
<<set _item.known = true>>
<<set _id = "#"+_index>>
<<replace _id>>
<div class="itembox">
<b>_item.name</b><br/>
<<print _item.info>><br/>
<span class="actdesc"><<print _item.desc>></span>
</div>
<</replace>>
<</link>>]
<</capture>>
<</if>>
</div>
</div>
<</for>>
<<else>>
It seems you don't have any unknown items. <<link "Try a different service...">>
<<replace "#business-area" t8n>><<include "decurse options">><</replace>>
<</link>>
<</if>><<set _x = $puppets.filter(function (puppet) { return puppet && puppet.hasCursedItem() === true; })>>
<<if _x.length > 0>>
<<link "Try a different service...">>
<<replace "#business-area" t8n>><<include "decurse options">><</replace>>
<</link>>
<br/>
<br/>
<<for _i, _p range _x>>
<<actorDisplay _p "decurse">>
<</for>>
<<else>>
It seems no one is currently saddled with any cursed items. <<link "Try a different service...">>
<<replace "#business-area" t8n>><<include "decurse options">><</replace>>
<</link>>
<</if>>/* Handles effect applications. */
<<widget "addEffect">>
/* Sets power for relevant effects and avoids duplicating an effect if it's already in the array. */
/* args0 is target, args1 is effect name, args2 is duration, args3 is inflictor (where applicable). If you want the power to be a flat value, pass a number to args3 instead and it will be used as an override. */
<<if $args.length > 0>>
<<set _target = $args[0]>> /* for clarity */
<<set _subject = $B.subject>>
<<print _target.addEffect($args[1],{time: $args[2], actor: $args[3]})>>
<<else>>
<<run console.log("ERROR in addEffect: no arguments passed")>>
<</if>> /* end args check */
<</widget>><<longreturn>>
MegaCorp® is happy to provide you with their top-of-the-line items, provided you have the cash. You have a limited budget, so choose which items you want to bring into your next battle. MegaCorp® will (with uncharacteristic generosity) fully refund you for any items you wish to exchange, so don't worry about going broke.
<b>Points: <<live $currency>></b>
<span id="items"><<include "item shop display">></span><<for _k, _v range $inventory>>
<<if _v.usable.includes("inbattle")>>
<div>
<b>_v.name</b> (Stock: _v.maxstock)
<div class="shop-button">
<<capture _v>>
<<if $currency >= _v.value && _v.maxstock < setup.ITEM_MAX>>
[<<link "BUY">>
<<replace "#items">>
<<run _v.maxstock++>>
<<set $currency -= _v.value>>
<<update>>
<<include "item shop display">>
<</replace>>
<</link>>]
<</if>>
<<if $currency >= _v.value && _v.maxstock > 0>> / <</if>>
<<if _v.maxstock > 0>>
[<<link "SELL">>
<<replace "#items">>
<<run _v.maxstock-->>
<<set $currency += _v.value>>
<<update>>
<<include "item shop display">>
<</replace>>
<</link>>]
<</if>>
<</capture>>
<b>Cost: _v.value</b>
</div>
</div>
<<print _v.info>><br/>
<span class="actdesc"><<print _v.desc>></span><br/>
<br/>
<</if>>
<</for>><<if _animationActive>>
<<include "animation activator">>
<</if>>
<<include "PassageDone-custom">><<include "PassageFooter-custom">><<if tags().includes("menu")>>
<<include "menubar">>
<</if>>
<<include "PassageHeader-custom">><<if $inbattle>>
<<chain>>
<<set $B.actors = $enemies.concat($puppets)>>
<</if>>
<<include "PassageReady-custom">><<include "hotkey definitions">>
<<set setup.formula = "subtractive">>
<<set setup.base = 80>> /* base constant for dmg formula */
<<set setup.damper = 1>> /* damping factor for dmg formula */
<<set setup.effbase = 30>> /* base constant for stat mods */
<<set setup.effdamper = 0.5>> /* damping factor for stat mods */
<<set setup.MIN_DMG = 1>> /* minimum damage */
<<set setup.min_DoT = 1>> /* minimum DoT damage */
<<set setup.min_buff = 0>> /* minimum buff effect */
<<set setup.min_debuff = 5>> /* minimum debuff effect */
/* Tooltip information associated with each stat, to be displayed in the status menu.
Also used elsewhere as a reference for the identity of core stats. */
<<set setup.statInfo = {
"Attack": "Increases damage of regular attacks by 1 per point.",
"Defense": "Reduces damage taken by 1 per point.",
"Special": "Improves effectiveness of status effects, mitigates received status ailments, and increases damage of item attacks by 1 per point.",
"Skill": "Increases critical hit chance by 1% per point.",
"Accuracy": "Increases attack accuracy by 1% per point.",
"Evasion": "Reduces incoming attack accuracy by 1% per point."
}>>
/* These stats will not be displayed in the menu or status pane even if they are core stats
Entries must match stat names exactly, case-sensitive */
<<set setup.hiddenStats = ["Skill","Accuracy","Evasion"]>>
/* Default equipment slots. Keys are the name of the slot, value is number of subslots (only considered if >1) */
<<set setup.DEFAULT_EQUIP_SLOTS = {
"Weapon": 1,
"Armor": 1,
"Accessory": 2
}>>
/* An array of all the elements that will appear in your game, in the order you wish for them to be displayed on the elemental resistances screen. The Actor constructor will automatically initialize everyone's values for these strings to 1. Note that any use of these elements must be set to EXACTLY the same string, case-sensitive. */
<<set setup.ELEMENT_LIST to ["black","white","red","blue","yellow"]>>
<<set setup.elementMessages = {
weakpoint: "Super effective!",
resist: "It's not very effective...",
immune: "No effect!",
absorb: "Absorbed!"
}>>
/* An array of all additional status screens you want displayed in the status box. There are separate lists for the menu status screen and the battle status pane, if you want different information displayed in each. If you set one of the attributes to an empty array or unset it entirely, the arrow buttons will not display at all, and the player will only see the default screen. */
<<set setup.STATUS_SCREENS to {
menu: ["equipment","elements","ailments"],
bestiary: ["stats","elements","ailments","rewards"],
battle: ["elements","ailments"]
}>>
<<set setup.MENU_OPTIONS = ["Status","Inventory","Equipment","Party"]>>
<<set $currency = 0>>
<<set setup.CURRENCY_NAME = "GP">>
/* This is the name that will be displayed every time the game refers to currency. By default it is the Dungeons & Dragons standard, GP for "gold pieces". */
/* Set whether to show animations in battle. */
<<set $ANIMATIONS = true>>
/* Set whether to show health meters in battle. */
<<set setup.SHOW_HEALTHBARS = true>>
<<set setup.ENEMY_BAR_COLOR = "#cc0000">>
<<set setup.PLAYER_BAR_COLOR = "green">>
<<set setup.MP_BAR_COLOR = "blue">>
<<set setup.HEALTH_BAR_TIME = "500ms">> /* animation duration for health bar updates (after taking damage, etc.) */
<<set setup.PORTRAITS = false>>
<<set setup.SHOW_MP = true>>
/* Set whether to display soak and flat regen in status screens. If you don't use either, you can set these to false for clarity. */
<<set setup.SOAK = true>>
<<set setup.FLAT_REGEN = true>>
/* Variables for determining lasting damage. By default, puppets are fully restored after every battle. See refreshPuppets for more info. */
<<set $lastingDamage = false>>
<<set $reviveAfterBattle = true>>
<<set $actionRefillAfterBattle = true>>
/* Initializing constants that will be used elsewhere. DON'T define variables that will be used in JavaScript database definitions here, as StoryInit is run after JavaScript initialization. */
<<set setup.ENregen = 2>>
<<set setup.STRUGGLE_COST = 2>>
<<set setup.BERSERK_FACTOR = 0.5>>
<<set setup.DEFEND_FACTOR = 0.5>>
<<set setup.SHIELD_FACTOR = 0.3>>
<<set setup.ITEM_COST = 2>> /* EN cost of using items */
<<set setup.RESPAWN_HP = 1>> /* proportion of HP that respawned characters will revive at */
<<set setup.THREAT_DECAY = 10>> /* threat decay per round */
<<set setup.ITEM_MAX = 9>> /* maximum of each item that can be stored in inventory */
<<set setup.PARTY_SIZE = 3>>
<<set setup.ACCURACY_RATE = true>> /* default accuracy of attacks in percent value or Boolean, default always-accurate */
<<set setup.MIN_ACCURACY = 0>> /* minimum accuracy rate if reduced by e.g. evasion stat */
<<set setup.CRITICAL_RATE = 0>> /* default critical hit rate in percent value, default 0% (no crits) */
<<set setup.CRITICAL_MULTIPLIER = 1.5>> /* default multiplier for critical hit damage */
<<set setup.CRISIS_FACTOR = (10/7)>> /* multiplied by dmg/maxhp to get crisis points per hit, at 1 target must take 100% max HP to fill Crisis */
<<set setup.DURATION_MAX = 9>> /* maximum turns a status effect can last */
<<set setup.MISS_MESSAGE = "$B.subject.name misses!">>
<<set setup.CRIT_MESSAGE = "<b>Critical hit!</b>">>
<<set setup.popupMod = {
weakpoint: "orange",
resist: "gray",
crit: "gold"
}
>>
<<set $difficulty to "hard">>
/*
Hard: smart targeting will only target vulnerable characters
Medium: smart targeting gives preference to vulnerable characters
Easy: no smart targeting
*/
<<set $inventory = new Inventory([])>>
/* This is a controller object. Use it to store any variables you don't want to persist after battles. Then, instead of having to remember and reset every variable, just reset this to a blank object. */
<<set $B = {}>>
/* Stores whether encounters have been fought. Useful if you want to be able to quickly refer to them e.g. to allow the player to progress after they have defeated an encounter in a certain passage. */
<<set $encounters = {}>>
/* Initializing miscellaneous variables. */
<<set $action to null>>
<<set $effects_to_remove = 0>>
<<set $removed_effects = []>>
<<set $tutorial to false>>
<<set $inbattle to false>>
<<set $LevelUps = []>>
/* Determines whether the action list shows full action data or not. If true, a tooltip box will appear at the top of the action list that will display full information on mouse hover. */
<<set $COMPRESSED_ACTIONS = true>>
/* Set whether to display the confirm phase in battle. This can be toggled by the user in the UI bar. */
<<set $SHOW_CONFIRM = true>>
/* Set whether to automatically end the player's turn. This can be toggled by the user in the UI bar. */
<<set $AUTO_ENDTURN = true>>
/* Set whether to use threat-based/aggro targeting. Turned off by default. */
<<set setup.THREAT_TARGETING = false>>
/* Set whether to use a 3x3 battle grid or a standard lineup. Turned off by default. */
<<set setup.BATTLE_GRID = false>>
/* Set whether to use turn exchange (force an enemy action for every player action). Turned off by default. */
<<set setup.TURN_EXCHANGE = false>>
/* Determine how multiple-element attacks calculate affinity factors.
-true: Damage modifier will be the average of all of the target's affinities
ex/ If target takes 0.5x damage from Red and 1x damage from Blue, a dual Red & Blue attack will inflict x0.75
-false: Damage modifier will find the best (for the attacker) of all the target's affinities
ex/ If target takes 0.5x damage from Red and 1x damage from Blue, a dual Red & Blue attack will inflict x1
*/
<<set setup.AVERAGE_ELEMENTS = true>>
/* Set whether to reveal all enemy data on kill. */
<<set setup.ENEMY_DATA_ON_KILL = true>>
/* Time values for hit animations, in ms. */
<<set setup.ANIM_WINDUP = 750;
setup.ANIM_DURATION = 750;
setup.DMG_DURATION = 1000;
setup.ANIM_DELAY = 600;>>
<<include "User-defined variables">>
/* Put any additional variables and code you want to run during StoryInit here so you don't have to juggle multiple files on a version update. */<<widget "martyrCheck">>
<<set _martyrTest = _party.find(function (p) { return p && p.martyr === true })>>
/* Note that this assumes only one Martyr can be active at a time. If multiple are active, this will only find the first in the array. */
<<if _martyrTest>>
<<set $B.target = _martyrTest>>
<<set _continue to false>>
<</if>>
<</widget>><<widget "protectionCheck">>
/* Checks if target is protected by someone with the Protector effect */
<<if $B.target !== null && $B.target.protectedBy>>
<<set _temp = $B.target.name>>
<<switch $B.target.id.charAt(0)>>
<<case "p">>
<<set _party = $puppets>>
<<case "e">>
<<set _party = $enemies>>
<<default>>
<<set _party = $puppets; console.log("ERROR in protectionCheck: invalid ID type")>>
<</switch>>
<<set $B.target = _party.find(function(t) { return t && t.id === $B.target.protectedBy; })>>
<<set _targetingMsg = $B.target.name+" took the hit for "+_temp+"!\n">>
<</if>>
<</widget>><<widget "guardCheck">>
/* Checks if target is blocked by another character (battle map style only) */
<<set _index = _party.indexOf(target())>>
<<if _index >= setup.ROW_SIZE && _party[_index-setup.ROW_SIZE] !== null && !_party[_index-setup.ROW_SIZE].dead>>
<<set $B.target = _party[_index-setup.ROW_SIZE]>>
<<elseif _index >= setup.ROW_SIZE * 2>>
<<set $B.target = _party[_index-setup.ROW_SIZE]>>
<<guardCheck>> /* Run again to test if the new mid-row target is in turn guarded by a front row character */
<</if>>
<</widget>><<widget "threatTarget">>
<<if _modifiers.includes("random")>>
/* truly random selection, ignores threat */
<<set $B.target = null>>
<<for target() === null>>
<<set _rand = random(0,puppets().length-1)>>
<<if !puppets()[_rand].dead && (_modifiers.includes("ignore untargetable") || !puppets()[_rand].untargetable)>>
<<set $B.target = puppets()[_rand]>>
<</if>>
<</for>>
<<set _continue = false>>
<</if>>
<<if _continue>>
<<set _totalThreat = 0>>
<<for _puppet range puppets().filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<set _threat = subject().threat.get(_puppet.name)>>
<<run _hitlist.push({target: _puppet, threat: _threat})>>
<<set _totalThreat += _threat>>
<</if>>
<</for>>
<<if _hitlist.length > 0>>
<<set _rand = random(1,_totalThreat)>>
<<for _i, _target range _hitlist>>
<<if _i != 0>>
<<set _target.threat += _hitlist[_i-1].threat>>
<<if _rand <= _target.threat && _rand > _hitlist[_i-1].threat>>
<<set $B.target = _target.target>>
<</if>>
<<elseif _rand <= _target.threat>>
<<set $B.target = _target.target>>
<</if>>
<</for>>
<<else>> /* everyone's dead, flag targetfail */
<<set _targetfail = true>>
<</if>>
<</if>> /* end random if */
<</widget>><<widget "randomTarget">>
<<if deadCount() == puppets().length || enemies().filter(function (e) { return e.dead; }).length == enemies().length>>
<<set _targetfail = true>>
<<else>>
<<set _modifiers = []>>
<<if $args[0] instanceof Array>>
<<set _modifiers = $args[0]>>
<<else>>
<<for _arg range $args>>
<<run _modifiers.push(_arg)>>
<</for>>
<</if>>
<<set _continue = true>>
/* Check if this widget is being run for a puppet under a loss-of-control effect. */
<<if subject() instanceof Puppet>>
<<if _modifiers.includes("any")>> /* if can target any party (e.g. confusion), pass "any" as an argument to the widget and target party will be selected randomly */
<<set _selector = random(1,2)>>
<<switch _selector>>
<<case 1>>
<<set _party = $puppets>>
<<case 2>>
<<set _party = $enemies>>
<</switch>>
<<elseif _modifiers.includes("enemies")>>
<<switch subject().id.charAt(0)>>
<<case 'p'>>
<<set _party = $enemies>>
<<case 'e'>>
<<set _party = $puppets>>
<</switch>>
<<elseif _modifiers.includes("allies")>>
<<switch subject().id.charAt(0)>>
<<case 'p'>>
<<set _party = $puppets>>
<<case 'e'>>
<<set _party = $enemies>>
<</switch>>
<</if>>
<<else>>
<<set _party = $puppets>>
<</if>>
<<set _party = _party.filter(function (a) { return a !== null; })>>
/* Priority 1: Martyr takes all hits */
<<martyrCheck>>
<<if _continue>>
/* Priority 2: aggro enemies bypass normal targeting */
<<if $B.subject.aggro && def $attacker>>
<<set $B.target = $puppets[$attacker]>> /* always targets the puppet that attacked last */
<<if !$B.target.dead>>
<<set _continue = false>>
<</if>>
/* Note that this bypasses untargetable as well */
<</if>>
<<if _continue>>
/* check if only untargetables remain */
<<if !_modifiers.includes("ignore untargetable")>>
<<set _untargetTest = _party.filter(function (p) { return (p.dead || p.untargetable) })>>
<<if _untargetTest.length == _party.length>>
<<run _modifiers.push("ignore untargetable")>>
<</if>>
<</if>>
<<set _hitlist = []>>
/* If subject is a Puppet, target will be selected completely randomly. By default, it is possible for a charmed or confused puppet to attack themselves. */
<<if subject() instanceof Puppet>>
<<set $B.target = null>>
<<for target() === null>>
<<set _rand = random(0,_party.length-1)>>
<<if !_party[_rand].dead && (_modifiers.includes("ignore untargetable") || !_party[_rand].untargetable)>>
<<set $B.target = _party[_rand]>>
<</if>>
<</for>>
<<else>>
<<if setup.THREAT_TARGETING === true>>
<<threatTarget>>
<<else>>
/* mercy setting */
<<if $B.subject.mercy < 1 or _modifiers.includes("smart")>>
/* if enemy's mercy is below 1, they will always use smart targeting */
<<set _mercy = 2>>
<<else>>
/* 1 in (mercy) chance of random targeting, to give players a break */
<<set _mercy = random(1,$B.subject.mercy)>>
<</if>>
/* smart targeting */
<<if $difficulty is "hard">>
<<if _mercy > 1>>
<<if !_modifiers.includes("ignore downed")>>
/* This clause makes enemies preferentially target off-balance and downed puppets. */
<<for _puppet range _party.filter(function (p) { return !p.dead && (p.offbalance || p.down); })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
<<if _modifiers.includes("debuff")>>
/* Debuffing attacks will preferentially target puppets with lowered Special, as they will be more strongly affected */
<<for _puppet range _party.filter(function (p) { return !(p.dead || p.chi || p.stasis) && p.get("Special") < p.getBase("Special"); })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
<<if _hitlist.length > 0>>
<<set _continue = false>>
<</if>>
<</if>>
/* normal targeting */
<<if _continue>>
<<for _puppet range _party.filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>>
<<elseif $difficulty is "medium">>
<<for _puppet range _party.filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
/* smart targeting */
<<if _mercy > 1>>
<<if !_modifiers.includes("ignore downed")>>
/* This clause makes enemies preferentially target off-balance and downed puppets. */
<<if _puppet.offbalance || _puppet.down>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</if>>
<<if _modifiers.includes("debuff")>>
/* Debuffing attacks will preferentially target puppets with lowered Special, as they will be more strongly affected */
<<if !_puppet.chi && && !_puppet.stasis && (_puppet.get("Special") < _puppet.getBase("Special"))>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</if>>
<</if>> /* end mercy if */
<</if>>
<</for>>
<<elseif $difficulty is "easy">>
<<for _puppet range _party.filter(function (p) { return !p.dead; })>>
<<if _modifiers.includes("ignore untargetable") || !_puppet.untargetable>>
<<run _hitlist.push(_puppet)>>
<</if>>
<</for>>
<</if>> /* end difficulty if */
<<if def _hitlist && _hitlist.length > 0>>
<<set _origHitlist = clone(_hitlist)>>
<<for _t range _origHitlist.filter(function (p) { return p.firefly === true; })>>
<<run _hitlist.push(_t)>>
<</for>>
<<set _n = random(0,_hitlist.length-1)>>
<<set $B.target = _hitlist[_n]>>
<<else>>
/* no target found, flag this to avoid errors elsewhere */
<<set _targetfail = true>>
<</if>>
<</if>> /* end threat vs. normal targeting if */
<</if>> /* end Puppet vs. Enemy if */
<</if>> /* end aggro if */
/* guard check (if using battle grid) */
<<if setup.BATTLE_GRID === true && !$action.ranged && !((subject() instanceof Puppet && target() instanceof Puppet) || (subject() instanceof Enemy && target() instanceof Enemy))>>
/* friendly fire bypasses this check */
<<guardCheck>>
<</if>>
/* protection check */
<<if !_modifiers.includes("ignore protection")>>
<<protectionCheck>>
<</if>>
<<unset _hitlist>>
<</if>> /* end martyr if */
<</if>> /* end initial targetfail check if */
<</widget>><<widget "allytarget">>
<<set _hitlist = []>>
<<for _actor range enemies().filter(function (e) { return !e.dead; })>>
<<if !($args.includes("noself") && _actor == subject())>>
<<if $args.includes("buff")>>
<<if !_actor.stasis>>
<<run _hitlist.push(_actor)>>
<</if>>
<<else>>
<<run _hitlist.push(_actor)>>
<</if>>
<</if>>
<</for>>
<<if _hitlist.length > 0>>
<<set _n = random(0,_hitlist.length-1)>>
<<set $B.target = _hitlist[_n]>>
<<unset _hitlist>>
<</if>>
<</widget>><<widget "dispelTarget">>
<<set _threshold = $args[0]>>
<<set _go = false>>
<<set _continue = true>>
<<set _temp = 0>>
<<if !$args.includes("mass")>>
/* Martyr check */
<<set _martyrTest = $puppets.find(function (p) { return p && p.martyr === true })>>
<<if _martyrTest>> /* will be false if no martyr found */
<<for _effect range _martyrTest.effects.filter(function (eff) { return (eff.buff && !eff.sticky) })>> /* Search for buffs */
<<if _effect.name == "Blessing">>
<<set _temp += 3>> /* Blessings are higher priority to dispel */
<<elseif _effect.name == "Martyr" || _effect.name == "Defender" || _effect.name == "Berserker">>
/* do nothing; these expire on the next round anyway, so not worth it */
<<else>>
<<set _temp += 1>>
<</if>>
<</for>>
<<if _temp >= _threshold>>
<<set $B.target = _martyrTest>>
<<set _continue = false>>
<<set _go = true>>
<<else>>
<<set _continue = false>>
<</if>>
<</if>>
<</if>>
<<if _continue>>
/* In order to pass, there must not have been a martyr */
<<set _hitlist = []>>
<<for _puppet range puppets().filter(function (p) { return !p.dead; })>>
<<if !$args.includes("mass")>>
<<set _temp = 0>>
<</if>>
<<if $args.includes("ignore untargetable") || !_puppet.untargetable>>
<<for _effect range _puppet.effects.filter(function (eff) { return (eff.buff && !eff.sticky) })>> /* Search for buffs */
<<if _effect.name == "Blessing">>
<<set _temp += 3>> /* Blessings are higher priority to dispel */
<<elseif _effect.name == "Martyr" || _effect.name == "Defender" || _effect.name == "Berserker">>
/* do nothing; these expire on the next round anyway, so not worth it */
<<else>>
<<set _temp += 1>>
<</if>>
<</for>>
<</if>>
<<if !$args.includes("mass")>>
<<if _temp >= _threshold>>
<<run _hitlist.push(_puppet)>> /* If someone has a buff, make them a possible target */
<</if>>
<</if>>
<</for>>
<<if (!$args.includes("mass") && _hitlist.length == 0) || ($args.includes("mass") && _temp < _threshold)>>
<<set _go = false>> /* If no one has any buffs, there's no point in using this; reroll */
<<else>>
<<set _go = true>>
<<if !$args.includes("mass")>> /* mass dispel doesn't need to pick a target */
<<set _n = random(0,(_hitlist.length-1))>>
<<set $B.target = _hitlist[_n]>>
/* Protection check. Comment this out if you do not want Protectors to protect against dispels. */
<<if !$args.includes("ignore protection")>>
<<protectionCheck>>
<</if>>
<</if>>
<</if>>
<</if>>
<</widget>><<widget "multihit">>
<<set _hits = $args[0]>>
<<for _h = 1; _h <= _hits; _h++>>
<<if $args[1] is "spread">> /* careful, this means you have to call randomTarget outside this widget if this isn't a spread attack */
<<randomTarget "ignore downed" "ignore untargetable">>
<</if>>
<<echoDamage>>
<</for>>
<</widget>><<widget "actorBox">>
/* Standard display for actor information in battle. */
/* arg 0 = actor object; arg 1 = class (defaults to "actor") */
<<run console.assert($args.length > 0 && $args[0] instanceof Actor,"ERROR in actorBox: no Actor passed")>>
<<set _actor = $args[0]>>
<<run console.assert(_actor instanceof Puppet || _actor instanceof Enemy,"ERROR in actorBox: actor is neither Puppet nor Enemy")>>
/* find actor's address in party array; will be used later */
<<if _actor instanceof Puppet>>
<<set _idx = $puppets.findIndex(function (a) { return _actor.id == a.id; })>>
<<set _nameID = "pname"+_idx>>
<<set _healthBar = $B.playerBars[_idx]>>
<<if !Meter.has(_healthBar)>>
<<newmeter _healthBar 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation setup.HEALTH_BAR_TIME linear>>
<<sizing 100%>>
<</newmeter>>
<</if>>
<<elseif _actor instanceof Enemy>>
<<set _idx = $enemies.findIndex(function (a) { return _actor.id == a.id })>>
<<set _nameID = "ename"+_idx>>
<<set _healthBar = $B.enemyBars[_idx]>>
<<if !Meter.has(_healthBar)>>
<<newmeter _healthBar 1>>
<<colors setup.ENEMY_BAR_COLOR>>
<<animation setup.HEALTH_BAR_TIME linear>>
<<sizing 100%>>
<</newmeter>>
<</if>>
<</if>>
<<set _boxID = "box"+_idx>>
/* determine container class; customized by arg 1, defaults to "actor" */
<<set _class = "actor ">>
<<if $args.length > 1 && typeof($args[1]) == "string">>
<<set _class += $args[1]>>
<</if>>
/* determine if name should be capitalized */
<<if _actor.caps>>
<<set _nameStyle = "text-transform:uppercase">>
<<else>>
<<set _nameStyle = "">>
<</if>>
<div @class=_class @id=_boxID>
/* Element 1: name */
<span class="actorname" @id=_nameID @style=_nameStyle>
<<include "Actor Box Name">>
</span>
/* Element 2: status button */
<<include "Actor Box Status">>
/* Element 3: HP */
<<include "Actor Box HP">>
/* Element 4: EN (Puppets only) */
<<include "Actor Box EN">>
/* Element 5: Crisis points */
<<include "Actor Box Crisis">>
/* Element 6: Status messages */
<<include "Actor Box Misc">>
<<if $ANIMATIONS === true && passage() == "Battle!" && _actor.battleMsg.length > 0>>
<<run console.log(_actor.name+" has a battle msg in master screen")>>
<<set _queue.add(_actor); _animationActive = true>>
<<for _x, _m range _actor.battleMsg>>
<div @id="'dmg'+_idx+'-'+_x" class="dmgPopup">
<<print _m.content>>
</div>
<</for>>
<</if>>
</div>
<</widget>><<if _actor.dead>>
/* Dead characters display their name and the † (dagger) symbol in place of the status button */
<<set _class = "statusbutton">>
<<if _actor.large>>
<<set _class += " absolute">>
<</if>>
<span class="dead">
_actor.fullname
<span @class=_class>†</span>
</span>
<<elseif _actor instanceof Puppet && $B.phase == "selection" && ndef _s && (!_actor.noact || _actor.down) && !_actor.isDone>>
/* For Puppets in the selection phase, their name becomes a link that sets them as the subject and allows the player to select their commands. */
<<capture _idx, _actor>>
<<link "_actor.name">>
<<set $B.subject = _actor>>
<<set _s = _idx>>
<<set $B.phase = "command">>
<<if setup.BATTLE_GRID === true>>
<<removeclass "#puppets" "grid">>
<</if>>
<<addclass "#enemies" "invisible">>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<<replace "#phase">><<include "commands">><</replace>>
<</link>>
<</capture>>
<<elseif $B.phase == "targeting">>
/* In the targeting phase, characters' names must become links to select them, but only if they are valid targets. */
<span class="targetnumber"><<print _idx+1>></span>
/* Check which party is being targeted: */
<<if ($B.targeting == "all" || (_actor instanceof Enemy && $B.targeting == "enemy") || (_actor instanceof Puppet && $B.targeting == "ally"))>>
/* Check if valid target.
For Puppets: Cannot target self if action has the noself property
For Enemies: Can only be targeted if
-Is a martyr OR
-No enemy has Martyr active
-The enemy is not untargetable
-(for battle grid) The enemy is not guarded OR the action is ranged
*/
<<if (_actor instanceof Puppet && !($B.subject.name == _actor.name && $B.noself === true)) ||
(_actor instanceof Enemy &&
((_actor.martyr || (!_martyr && !_actor.untargetable)) && ($action.ranged || guardCheck(_idx))))>>
/* If valid target, turn name into a link that assigns actor to the target variable, unsets the targeting varaible, and forwards the player to the next phase. We must also assign the ID variable determined earlier to a wrapper element to facilitate hotkey targeting. */
<<capture _actor>>
<<link "_actor.fullname">>
<<set $B.target = _actor; $B.targeting = null>>
<<replace "#phase">><<include "confirm phase">><</replace>>
<</link>>
<</capture>>
<<else>>
/* If invalid target, just display the name as normal. */
_actor.fullname
<</if>>
<<else>>
/* If not being targeted, just display the name as normal. */
_actor.fullname
<</if>>
<<elseif _actor instanceof Puppet && $B.phase == "move" && _actor != $B.subject>>
/* For Puppets in the movement phase, their name becomes a link that swaps their position with the subject (except for the subject's own name) */
<<capture _idx, _actor>>
<<link "_actor.name">>
<<set _m = $puppets.indexOf($B.subject)>>
<<set $puppets[_idx] = $B.subject; $puppets[_m] = _actor>>
<<replace "#puppets">><<include "actorlist puppets">><</replace>>
<</link>>
<</capture>>
<<else>>
/* In all other phases, just display the name as normal. */
_actor.fullname
<</if>><<if !_actor.dead>>
/* only display status button if character is not dead */
<<set _class = "statusbutton">>
<<if _actor.large>>
<<set _class += " absolute">>
<</if>>
<<capture _actor>><span @class=_class><<status _actor>></span><</capture>>
<</if>><<if !_actor.dead>>
<<capture _healthBar>>
<div>
HP: <<if _actor.maskhp>>???
<<else>><<print _actor.hp>><<if _actor.showMaxHP>> / _actor.maxhp<</if>>
<</if>>
</div>
<<if (!_actor.maskhp && setup.SHOW_HEALTHBARS)>>
<<if setup.BATTLE_GRID === true && _actor.large>>
<div class="largehealth">
<<showmeter _healthBar `_actor.hp / _actor.maxhp`>>
</div>
<<else>>
<<showmeter _healthBar `_actor.hp / _actor.maxhp`>>
<</if>>
<</if>>
<</capture>>
<</if>><<if !_actor.dead && _actor instanceof Puppet && !$args.includes("simplified")>>
<div>
EN: _actor.en / _actor.maxen
</div>
<</if>><<if !_actor.dead && def _s && _actor.crisis instanceof Array && _actor.crisis.length > 0 && !$args.includes("simplified")>>
/* Only display this if the character isn't currently selected (and if they have a valid Crisis in the first place) */
<<set _style = "font-size: 10pt; ">>
<<if _actor.crisisPoints >= 100>>
<<set _style += "color:red">>
<<else>>
<<set _style += "font-weight:normal">>
<</if>>
<div @style=_style>
Crisis: <<print _actor.crisisPoints>>%
</div>
<</if>><<if !_actor.dead>>
<div class="noact">
<<if $args.includes("simplified")>>
<br/>
<<elseif _actor.isDone>>
Done!
<<elseif _actor.stunned>>
Stunned!
<<elseif _actor.down>>
Prone!
<<else>>
<br/>
<</if>>
</div>
<</if>><<widget "fight">>
<<if $args.length > 0>>
<<if typeof($args[1]) == "string">>
<<set _text = $args[1]>>
<<else>>
<<set _text = "BATTLE">>
<</if>>
<center><<button _text "Preparation">><<set $scenario = $args[0]>><</button>></center>
<<else>>
<b>ERROR in fight: no arguments</b>
<</if>>
<</widget>><<widget "victorymessage">>
<<set _m = random(1,4)>>
<<switch _m>>
<<case 1>>
You win!
<<case 2>>
Victory!
<<case 3>>
You've won!
<<case 4>>
You are victorious! /* this is a reference to an old game, cookies if you recognize it */
<</switch>>
<</widget>><<widget "victorycheck">>
<<set _victory to 0>>
<<set _defeat to 0>>
<<for _enemy range enemies()>>
<<if _enemy.dead>>
<<set _victory++>>
<</if>>
<</for>>
<<if _victory == enemies().length>>
<<set $B.victory = true>>
<<goto "Victory">>
<</if>>
<<for _puppet range puppets()>>
<<if _puppet.dead>>
<<set _defeat++>>
<</if>>
<</for>>
<<if _defeat == puppets().length>>
<<set $B.defeat = true>>
<<goto "Defeat">>
<</if>>
<</widget>><<widget "refreshPuppets">>
/* Refreshes puppets to resting states. Use this to tidy up any flags or changes that might occur in battle. */
<<for _puppet range puppets()>>
<<set _puppet.isDone = false>>
<<set _puppet.inspired = false>>
<<set _puppet.lastAction = null>>
<<set _puppet.lastUsed = null>>
<<set _puppet.battleMsg = []>>
<<if !$lastingDamage || $reviveAfterBattle>>
<<set _puppet.dead = false>>
<<set _puppet.hp = Math.round(_puppet.maxhp * setup.RESPAWN_HP)>>
<</if>>
<<if !$lastingDamage>>
<<set _puppet.hp = _puppet.maxhp>>
<<set _puppet.en = 5>>
<</if>>
<<if !$actionRefillAfterBattle>>
<<for _action range _puppet.actions.filter(function (a) { return a.uses !== undefined })>>
<<run _action.refill()>>
<</for>>
<</if>>
<<for _action range _puppet.actions.filter(function (act) { return act.used === true })>>
<<run _action.used = false>>
<</for>>
<<for _action range _puppet.actions.filter(function (act) { return act.cd !== undefined })>>
<<run _action.cd = _action.warmup>>
<</for>>
<<for _effect range _puppet.effects.filter(function (eff) { return !eff.persistAfterBattle })>>
<<run _puppet.removeEffect(_effect, {pierce: true, unsticky: true})>>
<</for>>
<<for _k, _v range _puppet.tolerances>>
<<run _puppet.resetTol(_k)>>
<</for>>
<<if def _puppet._respawn>>
<<run _puppet._respawn.refill()>>
<</if>>
<<if def _puppet._retaliations>>
<<run _puppet._retaliations.refill()>>
<</if>>
<<set _x = _puppet.actions.find(function (a) { return a && a.name == "Reload" })>>
<<if def _x>>
<<set $B.subject = _puppet; _x.act()>>
<</if>>
<</for>>
<</widget>><<widget "chain">>
/*
<<for _i, _a range $args>>
<<if def $args[_i] and $args[_i] isnot null and $args[_i][1] isnot null>>
<<set $args[_i][0] = State.getVar($args[_i][1])>>
<</if>>
<</for>>
*/
<<if $B.target !== null>>
<<run getActor("target")>>
<</if>>
<<if $B.subject !== null>>
<<run getActor("subject")>>
<</if>>
<<if $B.actor !== null>>
<<run getActor("actor")>>
<</if>>
<</widget>><<widget "find">>
/* args0 is target array, args1 is attribute, args2 is key value */
/* Note that if your key is a string, it must be in quotes in the final code, which means you must send it to the widget with an extra pair of literal quotes (preceeded by the escape slash) */
<<print '<<set _pos = '+$args[0]+'.map(function(x) { return(x.'+$args[1]+') }).indexOf('+$args[2]+')>>'>>
<</widget>><<widget "longreturn">>
<<link "Return" $return>><</link>>
<</widget>><<widget "deathcheck">>
<<if $args.length > 0>>
<<set _targ = $args[0]>>
<<else>>
<<set _targ = target()>>
<</if>>
<<if _targ.hp <= 0 && !_targ.dead>>
<<if typeof(_targ.specialdeath) == "string" && !$B.specialdeath.includes(_targ.specialdeath)>>
<<run $B.specialdeath.push(_targ.specialdeath)>>
<<elseif !_targ.immortal>> /* This is necessary in the case of specialdeaths that don't actually kill the enemy, such as stage changes. If you want to run these cleanup functions, set the character's specialdeath flag to false and run deathcheck again in the special death passage. */
<<set _targ.dead to true>>
<<if _targ._deathMessage !== null>>
<<print _targ.deathMessage>>
<</if>>
<<if _targ instanceof Enemy>>
<<run $B.kills.push(_targ)>>
<<if def $bestiary && def $bestiary.fetch(_targ.name)>>
<<set $bestiary.fetch(_targ.name).defeated++>>
<<if setup.ENEMY_DATA_ON_KILL>>
<<run $bestiary.fetch(_targ.name).revealAll()>>
<</if>>
<</if>>
<<if typeof(subject().kills) == 'number'>>
<<set subject().kills++>>
<</if>>
<<set $B.XPreward += _targ.xp; $B.moneyReward += _targ.gp>>
<<if $B.enemyTurns > 0 && _targ.noAttacks > 1>>
<<run $B.enemyTurns -= 1>>
<</if>>
<<elseif _targ instanceof Puppet>>
<<run _targ.defeats++>>
<</if>>
/* Remove all effects unless they are meant to persist past unconsciousness */
<<for _k, _effect range _targ.effects.filter(function (eff) { return !eff.persistAfterDeath; })>>
<<run _targ.removeEffect(_effect,{pierce: true, unsticky: true, noPopup: true})>>
<</for>>
/* If defeated character was protected, their protector's protection effect must be removed */
<<if _targ.protectedBy !== null>>
<<switch _targ.protectedBy.charAt(0)>>
<<case "p">>
<<set _targets = $puppets>>
<<case "e">>
<<set _targets = $enemies>>
<<default>>
<<run console.log("ERROR in deathcheck: protected character's protector has invalid ID")>>
<</switch>>
<<set _temp = _targets.find(function(t) { return t && t.id === _targ.protectedBy; })>>
<<print _temp.removeEffect("Protector",{pierce: true})>>
<</if>>
<<elseif _targ.immortal>>
<<if _targ._deathMessage !== null>>
<<print _targ.deathMessage>>
<</if>>
<</if>>
<</if>>
<</widget>><<widget "playMusic">>
<<if $args.length > 0 && typeof($args[0]) == 'string'>>
<<set _trackId = $args[0].split(' ').join('_')>>
<<if !SimpleAudio.tracks.get(_trackId).isPlaying()>>
<<audio ":playing" stop>>
<</if>>
<<set $music = new Music($args[0])>>
<<if !$args.includes("instant") && !SimpleAudio.tracks.get(_trackId).isPlaying()>>
<<script>>
/* code provided by The Mad Exile */
var selector = State.temporary.trackId;
setTimeout(function () {
SimpleAudio.select(selector)
.loop(true)
.volume(1)
.play();
}, 500); /* in milliseconds */
<</script>>
<<else>>
<<audio _trackId volume 1 play loop>>
<</if>>
<<if document.getElementById("musicInfo")>>
<<replace "#musicInfo">><<include "music info">><</replace>>
<</if>>
<<else>>
<<run console.log("ERROR in playMusic: non-string argument passed")>>
<</if>>
<</widget>><<widget "clearMusic">>
<<audio ":playing" volume 1 stop>>
<<unset $music>>
<<if document.getElementById("musicInfo")>>
<<replace "#musicInfo">><<include "music info">><</replace>>
<</if>>
<</widget>><<widget "typewriter">>
<!-- Create a SPAN with an ID -->
<span id="typewriter"></span>
<!-- In SugarCube, arrays start at 0 -->
<<set _textArrayLength to 0>>
<!-- Repeat every second -->
<<repeat $args[1]>>
<!-- Test if textArrayLength is greater than length of $args[0] -->
<<if _textArrayLength gte $args[0].length>>
<<stop>>
<<else>>
<!-- Append the current position to the existing characters -->
<<append "#typewriter">>$args[0][_textArrayLength]<</append>>
<!-- Update the length -->
<<set _textArrayLength++>>
<</if>>
<</repeat>>
<</widget>><<widget "neutralize">>
/* DEPRECIATED as of v1.01. Use the dispelCalc() function in the actions database instead. */
/* Determines effects to be removed by Neutralize and Restoration. */
/* Because these actions are so similar, the same widget can be used for both. */
<<if $B.target.stasis isnot true>>
/* Stasis blocks any effect changes, so it blocks this too. */
<<set _effects to $B.target.effects>> /* This is done just so you don't have to write out the longer name */
<<for _i = (_effects.length-1); _i >= 0; _i-->>
/* Loop runs over the effects array starting from the END and working backwards. This is why we can't use a "range" loop, because that only goes forwards. */
<<if $effects_to_remove <= 0>>
<<break>>
/* The number of effects removed by Neutralize/Restoration varies depending on energy invested. If there are more effects than the spell can remove, we end the function here. Otherwise the spell would clear all effects regardless of strength! */
<</if>>
<<if $B.target instanceof Puppet is true>>
<<if _effects[_i].buff isnot true and _effects[_i].sticky isnot true>>
/* Because there are fewer buffs than debuffs, a single "buff" flag is used to distinguish them. We only want Restoration to remove debuffs, so it will only trigger the removal code if the effect's buff flag is NOT true. */
/* You may also want some effects to be irremovable. This is the purpose of the "sticky" flag, which is set in the story JavaScript. */
<<run $removed_effects.push(_effects[_i])>>
<<set $effects_to_remove -= 1>>
<</if>>
<<elseif $B.target instanceof Enemy is true>>
<<if _effects[_i].buff is true and _effects[_i].sticky isnot true>>
<<run $removed_effects.push(_effects[_i])>>
<<set $effects_to_remove -= 1>>
<</if>>
<</if>>
<</for>>
<</if>>
<</widget>>
<<widget "deathcheckOLD">>
/* Old death check method that runs over all characters. Depreciated. */
<<for _enemy range $enemies>>
<<if _enemy.hp <= 0 and _enemy.dead is false>>
<<set _enemy.hp = 0>>
<<run $B.kills.push(_enemy.name)>>
<<if _enemy.deathMessage is "special">>
<<print "\n\n...">>
<<set $B.specialdeath++>>
<<else>>
<<set _enemy.dead to true>>
<<print _enemy.deathMessage>>
<<for _effect range _enemy.effects>>
<<set _effect.duration to 0>>
<</for>>
<<effectmanager _enemy>> /* to strip effect flags, just in case they can still interfere during the turn */
<</if>>
<</if>>
<</for>>
<<for _puppet range puppets()>>
<<if _puppet.hp <= 0 and _puppet.dead is false>>
<<set _puppet.hp = 0>>
<<set _puppet.dead to true>>
<<print _puppet.deathMessage>>
<<for _effect range _puppet.effects>>
<<set _effect.duration to 0>>
<</for>>
<<effectmanager _puppet>>
<</if>>
<</for>>
<</widget>>
<<widget "removeEffect">>
/* DEPRECIATED as of v0.91. Use Actor.removeEffect instead. */
<<set _actor = $args[0]>>
<<set _effect = $args[1]>>
<<if _actor.stasis is false or $args.includes("pierce")>>
<<set _effect.duration = 0>>
<<effectmanager _actor>>
<</if>>
<</widget>>/* Restock: sets starting inventory values for each battle. */
<<widget "restock">>
<<for _k, _v range $inventory>>
<<set _v.stock = _v.maxstock>>
<</for>>
<</widget>><<widget "itemDrop">>
<<set _item = new Item($args[0])>>
<<if def $args[1] && typeof($args[1]) == 'number'>>
<<set _amt = $args[1]>>
<<else>>
<<set _amt = 1>>
<</if>>
<<set _added = $inventory.addItem($args[0],_amt)>>
<center>
<div class="itembox">
<b>_item.name<span style="float:right">x<<print _amt>></span></b><br/>
<<print _item.info>><br/>
<span class="actdesc"><<print _item.desc>></span>
</div>
<<if _added === false>>
<i>You can't hold any more of these! Use or sell some and then come back.</i>
<</if>>
</center>
<</widget>><<widget "effectinfo">>
<<for _k, _effect range $B.actor.effects>>
<<if _effect.invisible isnot true>>
<div class="effectinfo">
_effect.name
<<if _effect.duration >= 0>>
<span class="duration">_effect.duration turn<<if _effect.duration > 1>>s<</if>></span>
<</if>><br/>
<span class="effectdesc">
<<print _effect.info(_effect)>>
</span>
</div><br/>
<</if>>
<</for>>
<</widget>><<widget "stat">>
/* Designed by greyelf */
/* Check that a Stat Name was passed to the widget. */
<<if $args.length is 0>>
ERROR
<<else>>
<<set _current to $B.actor.get($args[0]) >>
<<set _base to $B.actor.getBase($args[0]) + $B.actor.getEquipBonus($args[0]) >>
/* Check if the Stat has been raised. */
<<if _current > _base >>
@@.stat-raised;_current@@
/* Check if the Stat has been lowered. */
<<elseif _current < _base >>
@@.stat-lowered;_current@@
/* The Stat has not changed. */
<<else>>
_current
<</if>>
<</if>>
<</widget>><<widget "res">>
/* Check that an argument was passed to the widget. */
<<if $args.length is 0>>
ERROR
<<else>>
<<set _r = $args[0].percent.current>>
<<set _r = Math.round((_r - 1) * -100)>>
/* Weakpoint? */
<<if _r < 0 >>
@@.stat-lowered;<<print _r>>%@@
/* Absorbance? */
<<elseif _r > 100 >>
<<run _r -= 100>>
@@.green;+<<print _r>>%@@
/* Resistance? */
<<elseif _r > 0 >>
@@.stat-raised;<<print _r>>%@@
<<else>>
<<print _r>>%
<</if>>
<</if>>
<</widget>><<widget "soak">>
<<if $args.length is 0>>
ERROR
<<else>>
<<set _r = $args[0].flat.current>>
/* Weakpoint? */
<<if _r < 0 >>
@@.stat-lowered;<<print _r>>@@
/* Resistance? */
<<elseif _r > 0 >>
@@.stat-raised;<<print _r>>@@
<<else>>
<<print _r>>
<</if>>
<</if>>
<</widget>><<widget "tol">>
<<if $args.length is 0>>
ERROR
<<else>>
<<run console.log("Rendering tolerance. Max value = "+_v.current)>>
<<run console.log("Rendering tolerance. Current value = "+_v.currentVal)>>
/* Unlike other status widgets, this is passed the value, not the key. */
<<set _v = $args[0]>>
<span class="tolerance">
/* Immunity? */
<<if _v.current < 0>>
X
/* Tolerance? */
<<elseif _v.currentVal > 0>>
<<print _v.currentVal>>
/* Tolerance exhausted? */
<<elseif _v.currentVal == 0>>
!
<</if>>
</span>
<</if>>
<</widget>><<widget "statOOB">>
/* For displaying stats outside of battle. Base and equipment mods are displayed separately here. If you wish for effect mods to persist outside of battle, you could include them as well. */
<<if $args.length is 0>>
ERROR
<<else>>
<<set _p = $args[1]>>
<<set _mod to _p.getBonus($args[0]) >>
<<set _base to _p.getBase($args[0])>>
<<print _base>>
/* Check if equipment mod is positive. */
<<if _mod > 0 >>
@@.green;<<print "\+"+_mod>>@@
/* Check if equipment mod is negative. */
<<elseif _mod < 0 >>
@@.stat-lowered;<<print "-"+_mod>>@@
<<elseif _mod == 0>>
/* display nothing */
<</if>>
<</if>>
<</widget>>/* Special actions are added to the action queue. If the action queue contains any actions, this passage will activate instead of the normal destination when the player clicks "continue". */
/* Assumes that elements of the action queue are two-element arrays with the subject's ID in the 0th element and the action as the 1st element. */
<<if $B.actionQueue.length > 0>>
<<set _data = $B.actionQueue.shift()>>
<<set _OG = {target: $B.target, subject: $B.subject, action: $action}>>
<<set $B.subject = getActorById(_data[0]); $action = _data[1]>>
<<if !(subject().dead || subject().noact)>>
<<if $action.counter>>
<<set $B.target = _OG.subject>>
<<set _counterActive = true>>
<</if>>
<<include "action effects">><br/>
<<set _counterActive = false>>
<<else>>
<<timed 0s>><<trigger 'click' "#continue button">><</timed>>
<</if>>
<</if>>
<<include "Battle Continue Button">><<widget "hunterCheck">>
/* Called in the enemy phase, after every enemy's action. */
<<unset _party>>
<<if $B.target instanceof Puppet && $B.subject instanceof Enemy>>
<<set _party = $puppets>>
<<elseif $B.target instanceof Enemy && $B.subject instanceof Puppet>>
<<set _party = $enemies>>
<</if>>
<<if def _party>>
<<for _actor range _party>>
<<if _actor.hunter && !(_actor.dead || _actor.noact || _actor.uncontrollable)>>
<<set $B.actionQueue.push([_actor.id,new Action("Hunter Counter")])>>
<</if>>
<</for>>
<</if>>
<</widget>><<widget "markAttack">>
/* Called in the action phase, after action is finished. */
<<set _archer = $puppets.
find(function (p) { return p && p.name == "Archer" && !(p.dead || p.noact || p.uncontrollable) })>>
<<if def _archer
&& $B.subject instanceof Puppet
&& subject().name != "Archer"
&& $B.target instanceof Enemy
&& _archer.en > 0
&& enemies().filter(function (e) { return e.marked && !e.dead }).length > 0>>
<<set _markActive = true>>
<<run $B.actionQueue.push([_archer.id,new Action("Mark Shot")])>>
<</if>>
<</widget>><<if _animationActive && _queue.size > 0 && $("#continue") !== undefined>>
<<script>>
$("#continue .macro-button").each((index, element) => {
element.disabled = true;
});
<</script>>
<</if>>
<<set _animationsToComplete = 0>>
<<set _animationsComplete = 0>>
<<timed setup.ANIM_WINDUP+"ms">>
<<for _i, _p range _queue>>
<<set _idA = "#box"+_i>>
<<capture _idA, _p>>
<<for _x, _m range _p.battleMsg>>
<<if passage() == "Battle!">>
<<if _p instanceof Enemy>>
<<set _idx = $enemies.findIndex(function (a) { return _p.id == a.id; })>>
<<elseif _p instanceof Puppet>>
<<set _idx = $puppets.findIndex(function (a) { return _p.id == a.id; })>>
<</if>>
<<set _idB = "#dmg"+_idx+'-'+_x>>
<<else>>
<<set _idB = "#dmg"+_i+'-'+_x>>
<</if>>
<<capture _x, _idB, _m>>
<<set _time = (_x*setup.ANIM_DELAY)+"ms">>
<<if _m.shake>> /* if true, box shakes */
<<timed _time>>
<<if $(_idA).hasClass("animate__animated")>>
<<run resetAnimation(_idA,"headShake")>>
<<else>>
<<run animateCSS(_idA,"headShake",setup.ANIM_DURATION+"ms")>>
<</if>>
<<include "popup animation">>
<</timed>>
<<else>>
<<timed _time>>
<<include "popup animation">>
<</timed>>
<</if>>
<</capture>>
<</for>>
<</capture>>
<<set _animationsToComplete += _p.battleMsg.length>>
<<set _p.battleMsg = []>>
<</for>>
<</timed>><<set _animation = "slideOutUp">>
<<switch _m.type>>
<<case "damage">>
<<set _p.hp -= _m.content>>
<<case "healing">>
<<run $(_idB).addClass("green")>>
<<set _p.hp += _m.content>>
<<case "regen">>
<<run $(_idB).addClass("regen")>>
<<run $(_idB).addClass("small")>>
<<case "block">>
<<run $(_idB).addClass("small")>>
<<case "addEffect">>
<<run $(_idB).addClass("small")>>
<<run $(_idB).addClass("stat-raised")>>
<<case "removeEffect">>
<<run $(_idB).addClass("small")>>
<<run $(_idB).addClass("maroon")>>
<<set _animation = "slideOutDown">>
<</switch>>
<<update>>
<<if typeof(_m.mod) == "string">>
<<run console.log("mod for "+_idB+" is "+_m.mod)>>
<<run $(_idB).addClass(_m.mod)>>
<</if>>
<<set _animationsComplete++>>
<<run animateCSS(_idB,_animation,setup.DMG_DURATION+"ms")>><<event keydown>>
<<which 81>> /* Q pressed */
<<if $inbattle>>
<<switch $B.phase>>
<<case "command">>
<<if $B.subject !== null>>
<<set _id = "#actbtn a">>
<<trigger 'click' _id>>
<</if>>
<<case "actions">>
<<if def $B.subject.defaultAction && $B.subject.defaultAction !== null>>
<<set _action = $B.subject.defaultAction>>
<<actionLink>>
<</if>>
<<case "confirm">>
<<trigger 'click' "#confirmLink a">>
<<case "selection">>
/* do nothing */
<<default>>
<<trigger 'click' "button.macro-button">>
<</switch>>
<<elseif passage() == "Menu: Inventory" && def _display>>
<<set _id = '#button1 button'>>
<<trigger 'click' _id>>
<</if>>
<<which 87>> /* W pressed */
<<if $inbattle>>
<<run console.log("W handler: inbattle")>>
<<switch $B.phase>>
<<case "command">>
<<if $B.subject !== null>>
<<set _id = "#restbtn a">>
<<trigger 'click' _id>>
<</if>>
<<case "actions">>
<<if $B.subject !== null && $B.subject.lastAction instanceof Action>>
<<set _action = $B.subject.lastAction>>
<<actionLink>>
<</if>>
<</switch>>
<<elseif passage() == "Menu: Inventory" && def _display>>
<<set _id = '#button2 button'>>
<<trigger 'click' _id>>
<<elseif !tags().includes("nomenu") && !tags().includes("noreturn")>>
<<run console.log("W handler: tags do not include nomenu or noreturn")>>
<<set $menu_screen = 0>>
<<goto "Menu: Status">>
<</if>>
<<which 69>> /* E pressed */
<<if $inbattle>>
<<switch $B.phase>>
<<case "command">>
<<if $B.subject !== null>>
<<set _id = "#itembtn a">>
<<trigger 'click' _id>>
<</if>>
<<case "actions">>
<<if document.getElementById("crisisLink")>>
<<set _id = "#crisisLink a">>
<<trigger 'click' _id>>
<</if>>
<<case "crisis">>
<<set _id = "#regularActions a">>
<<trigger 'click' _id>>
<</switch>>
<</if>>
<<which 82>> /* R pressed */
<<if $inbattle>>
<<switch $B.phase>>
<<default>>
<<if $('#battlebackbtn').length > 0>>
<<trigger 'click' '#battlebackbtn a'>>
<</if>>
<</switch>>
<<else>>
<<if passage() == "Menu: Status" && def _display>>
<<trigger 'click' '#toggle a'>>
<<elseif passage() == "Menu: Equipment" && def _s>>
<<trigger 'click' '#toggle a'>>
<<elseif passage() == "Menu: Inventory" && document.getElementById("cancelbutton")>>
<<set _id = '#cancelbutton button'>>
<<trigger 'click' _id>>
<<elseif passage() == "Menu: Bestiary" && def _display>>
<<trigger 'click' '#toggle a'>>
<<else>>
<<trigger 'click' '#menu-return a'>>
<</if>>
<</if>>
<<which 65>> /* A pressed */
<<if $inbattle || passage() == "Menu: Status" && def _display>>
<<trigger 'click' '#statusback a'>>
<<elseif passage() == "Menu: Bestiary" && def _display>>
<<trigger 'click' '#lastentry a'>>
<<else>>
<<set $menu_screen-->>
<<if $menu_screen < 0>>
<<set $menu_screen = setup.MENU_OPTIONS.length-1>>
<</if>>
<<set _menupos = "#menu"+$menu_screen+" a">>
<<trigger 'click' _menupos>>
<</if>>
<<which 68>> /* D pressed */
<<if $inbattle || passage() == "Menu: Status" && def _display>>
<<trigger 'click' '#statusforward a'>>
<<elseif passage() == "Menu: Bestiary" && def _display>>
<<trigger 'click' '#nextentry a'>>
<<else>>
<<set $menu_screen++>>
<<if $menu_screen >= setup.MENU_OPTIONS.length>>
<<set $menu_screen = 0>>
<</if>>
<<set _menupos = "#menu"+$menu_screen+" a">>
<<trigger 'click' _menupos>>
<</if>>
<<which 49>> /* 1 pressed */
<<numKey 0>>
<<which 50>> /* 2 pressed */
<<numKey 1>>
<<which 51>> /* 3 pressed */
<<numKey 2>>
<<which 52>> /* 4 pressed */
<<numKey 3>>
<<which 53>> /* 5 pressed */
<<numKey 4>>
<<which 54>> /* 6 pressed */
<<numKey 5>>
<<which 55>> /* 7 pressed */
<<numKey 6>>
<<which 56>> /* 8 pressed */
<<numKey 7>>
<<which 57>> /* 9 pressed */
<<numKey 8>>
<<which 48>> /* 0 pressed */
<<numKey 9>>
<<which 16>> /* Shift pressed */
<<if $inbattle>>
<<switch $B.phase>>
<<case "targeting">>
<<if _targetingEnemy === true>>
<<set _targetingEnemy = false>>
<<elseif _targetingEnemy === false>>
<<set _targetingEnemy = true>>
<</if>>
<<if $('#target_help').length > 0>>
<<replace '#target_help'>>[Hotkeys targeting <<if _targetingEnemy === true>>enemies<<else>>allies<</if>>. Press Shift to switch targets.]<</replace>>
<</if>>
<</switch>>
<</if>>
<<which 77>> /* M pressed */
<<if $muted === true>>
<<masteraudio unmute>>
<<set $muted = false>>
<<replace "#mutebutton">><<include "mute button">><</replace>>
<<else>>
<<masteraudio mute>>
<<set $muted = true>>
<<replace "#mutebutton">><<include "mute button">><</replace>>
<</if>>
<</event>><<widget "numKey">>
/* Functionality for number hotkeys. By default, used to select characters in selection screens. */
<<run console.assert($args.length > 0 && Number.isInteger($args[0]),"ERROR in numKey: key is undefined or non-integer")>>
<<set _n = $args[0]>>
<<if $inbattle>>
<<switch $B.phase>>
<<case "selection">>
<<if def $puppets[_n] && $puppets[_n] !== null && !$puppets[_n].isDone && !$puppets[_n].dead>>
<<set _id = "#pname"+_n+" a">>
<<trigger 'click' _id>>
<</if>>
<<case "targeting">>
<<switch $B.targeting>>
<<case "enemy">>
<<if !$enemies[_n].hidden>>
<<set _party = $enemies>>
<<set _id = "#ename">>
<</if>>
<<case "ally">>
<<set _party = $puppets>>
<<set _id = "#pname">>
<<case "all">>
<<if _targetingEnemy === true>>
<<set _party = $enemies>>
<<set _id = "#ename">>
<<elseif _targetingEnemy === false>>
<<set _party = $puppets>>
<<set _id = "#pname">>
<</if>>
<</switch>>
<<if def _party>>
<<set _id += _n+' a'>>
<<trigger 'click' _id>>
<</if>>
<</switch>>
<<elseif passage() == "Menu: Status" && ndef _display>>
<<set _id = '#'+_n+' a'>>
<<trigger 'click' _id>>
<<elseif passage() == "Menu: Equipment" && ndef _s>>
<<set _id = '#'+_n+' a'>>
<<trigger 'click' _id>>
<</if>>
<</widget>><div class="hotkey monospace" style="text-align:left">[Q] = confirm/advance
[R] = cancel/back
[W] = <<nobr>><<link "menu">>
<<if !$inbattle && !tags().includes("nomenu") && !tags().includes("noreturn") && !_nomenu>>
<<set $menu_screen = 0>>
<<goto "Menu: Status">>
<</if>>
<</link>><</nobr>>
[A] = cycle menu left
[D] = cycle menu right</div><<set _initiative = -1>>
/* The -1 is necessary if you want a Speed of 0 to be possible. */
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead && !a.noact; })>>
<<if !_actor.isDone && _actor.get("Speed") > _initiative>>
/* (!_actor.isDone) prevents us from selecting characters who have already acted this round. They'll be skipped over, and the next-fastest character will get the initiative. */
<<set _subj = _actor>>
<<set _initiative = _actor.get("Speed")>>
/* This sets the current character's Speed as the new bar to clear. If no one's faster, no one else will pass the if check above and this character will remain the current subject. If someone else is faster, they'll become the subject and the _initiative variable will be updated to match their Speed. This ensures that subject status will be granted to the fastest character. */
<<elseif !_actor.isDone && _actor.get("Speed") == _initiative>>
/* You'll need a handler for this case, or else the character with the higher index order will get the initiative in the case of a match. This handler can be anything, including nothing at all. If you want to be nice, you could automatically give the player the initiative in the case of a tie. Here I've provided the fairest possible option: a coin flip. */
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = _actor>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<set $B.subject = _actor>>
<<if subject() instanceof Enemy>>
<<set $B.turn = "enemy">>
<</if>>
<</if>><<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead && !a.noact; })>>
<<set _actor.initiative = _actor.get("Speed")>>
<<set _variance = random(-$VARIANCE_BOUND,$VARIANCE_BOUND)>>
<<set _variance /= 100>>
/* Twine's random() function requires integer bounds, but we (presumably) plan to vary characters' Speed stats by a small proportion. We can do this by setting $VARIANCE_BOUND to an integer percentage value (probably in StoryInit) and dividing by 100 to get the decimal value. */
<<set _actor.initiative += _actor.get("Speed") * _variance>>
<</for>>
<</for>>
<<set _initiative = -1>>
/* The -1 is necessary if you want a Speed of 0 to be possible. */
<<for _i, _party range $actors>>
<<for _j, _actor range _party.filter(function (a) { return a !== null && !a.dead && !a.noact; })>>
<<if not _actor.isDone and _actor.initiative > _initiative>>
/* (not _actor.isDone) prevents us from selecting characters who have already acted this round. They'll be skipped over, and the next-fastest character will get the initiative. */
<<set _subj = _actor>>
<<set _initiative = _actor.initiative>>
/* This sets the current character's initiative as the new bar to clear. If no one's faster, no one else will pass the if check above and this character will remain the current subject. If someone else is faster, they'll become the subject and the _initiative variable will be updated to match their Speed. This ensures that subject status will be granted to the fastest character. */
<<elseif not _actor.isDone and _actor.initiative == _initiative>>
/* You'll need a handler for this case, or else the character with the higher index order will get the initiative in the case of a match. This handler can be anything, including nothing at all. If you want to be nice, you could automatically give the player the initiative in the case of a tie. Here I've provided the fairest possible option: a coin flip. */
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = _actor>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<set $B.subject = _actor>>
<<if subject() instanceof Enemy>>
<<set $B.turn = "enemy">>
<</if>>
<</if>>/* initiative gain */
/* Note this model assumes everyone's "initiative" attribute is initialized to 0 at battle start. */
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<set _actor.initiative += _actor.get("Speed")>>
<</for>>
<</for>>
/* initiative comparison */
<<set _initiative = -1>>
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<if _actor.initiative > _initiative>>
<<set _subj = _actor>>
<<set _initiative = _actor.initiative>>
<<elseif _actor.initiative == _initiative>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = _actor>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<set $B.subject = _actor; subject().initiative = 0>>
<<if subject() instanceof Enemy>>
<<set $B.turn = "enemy">>
<</if>>
<</if>>/* Note that this assumes everyone's "initiative" attribute is set to 0 at battle start. */
<<set _threshold = setup.ACTION_THRESHOLD>> /* You would set ACTION_THRESHOLD to some constant in StoryInit. */
<<set _pastThreshold = false>>
/* check for characters already at threshold */
<<for _party range $actors>>
<<if _pastThreshold>>
<<break>>
<</if>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<if _actor.initiative >= _threshold>>
<<set _pastThreshold = true>>
<<break>>
<</if>>
<</for>>
<</for>>
/* initiative gain */
<<for not _pastThreshold>>
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<set _actor.initiative += 20 + 0.2 * _actor.get("Speed")>>
/* This is the Bonfire formula, but you can add your own */
<<if _actor.initiative >= _threshold>>
<<set _pastThreshold = true>>
<</if>>
<</for>>
<</for>>
<</for>>
/* initiative comparison */
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<if _actor.initiative > _threshold>>
<<set _subj = _actor>>
<<set _threshold = _actor.initiative>>
<<elseif _actor.initiative == _threshold>>
<<if def _subj>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = _actor>>
<</if>>
<<else>>
<<set _subj = _actor>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<set $B.subject = _actor; subject().initiative -= setup.ACTION_THRESHOLD>>
<<if subject() instanceof Enemy>>
<<set $B.turn = "enemy">>
<</if>>
<</if>>/* Note that you will need to come up with a method for determining everyone's starting initiative; that is not modeled here. */
<<set _threshold = 0>>
<<set _pastThreshold = false>>
/* check for characters already at threshold */
<<for _party range $actors>>
<<if _pastThreshold>>
<<break>>
<</if>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<if _actor.initiative <= _threshold>>
<<set _pastThreshold = true>>
<<break>>
<</if>>
<</for>>
<</for>>
/* initiative gain */
<<for not _pastThreshold>>
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<set _actor.initiative -= 1>>
<<if _actor.initiative <= _threshold>>
<<set _pastThreshold = true>>
<</if>>
<</for>>
<</for>>
<</for>>
/* initiative comparison */
<<for _party range $actors>>
<<for _actor range _party.filter(function (a) { return a !== null && !a.dead; })>>
<<if _actor.initiative < _threshold>>
<<set _subj = _actor>>
<<set _threshold = _actor.initiative>> /* This is only necessary if it is possible to overshoot the threshold */
<<elseif _actor.initiative == _threshold>>
<<if def _subj>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _subj = _actor>>
<</if>>
<<else>>
<<set _subj = _actor>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if ndef _subj>>
(You should write an error message here.)
<<else>>
<<set $B.subject = _actor; subject().initiative -= setup.ACTION_THRESHOLD>>
<<if subject() instanceof Enemy>>
<<set $B.turn = "enemy">>
<</if>>
<</if>>/* Turn preview for the ranked order model. Run this at the start of each round. */
<<set $turn_preview = []>>
<<set _count = 0>>
<<for _party range $actors>>
<<for _actor range _party>>
<<if not _actor.dead and not _actor.stunned>>
<<run _count++>>
<</if>>
<</for>>
<</for>>
<<for $turn_preview.length < _count>>
<<set _initiative = -1>>
<<for _i, _party range $actors>>
<<for _j, _actor range _party>>
<<if not _actor.dead and not _actor.stunned>>
<<if not $turn_preview.includes(_actor.name) and _actor.initiative > _initiative>>
<<set _next = _actor.name>>
<<set _initiative = _actor.initiative>>
<<elseif not $turn_preview.includes(_actor.name) and _actor.initiative == _initiative>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _next = _actor.name>>
<</if>>
<</if>>
<</if>>
<</for>>
<</for>>
<<run $turn_preview.push(_next)>>
<</for>>
/* You can then draw from $turn_preview to list the characters. The array is already sorted, so you can just use a for loop to display the characters in the order of their action. Exactly how this is implemented is left up to you. */
/* You will still need to run the regular program to choose the active character thanks to Twine breaking object references. Otherwise, you could just add each _actor to the array and draw from there. */
/* Known bug: in case of equal initiatives, the coin flip may go differently here than it does in the actual selection. */<<set $turn_preview = []>>
<<set _preview = clone($actors)>>
/* This is only a simulation, so we make a clone of the current state of $actors to work with. */
<<for _i = 0; _i < $PREVIEW_PROGNOSIS; _i++>>
/* $PREVIEW_PROGNOSIS is how many turns in advance you would like to calculate. I don't recommend making this very high. */
/* initiative gain */
<<for _party range _preview>>
<<for _actor range _party>>
<<if not _actor.dead>>
<<set _actor.initiative += _actor.speed>>
<</if>>
<</for>>
<</for>>
/* initiative comparison */
<<set _initiative = -1>>
<<for _i, _party range _preview>>
<<for _j, _actor range _party>>
<<if not _actor.dead>>
<<if _actor.initiative > _initiative>>
<<set _next = _actor>>
<<set _initiative = _actor.initiative>>
<<elseif _actor.initiative == _initiative>>
<<set _r = random(1,2)>>
<<if _r == 1>>
<<set _next = _actor>>
<</if>>
<</if>>
<</if>>
<</for>>
<</for>>
<<run $turn_preview.push(_actor)>>
<<set _next.initiative = 0>>
<</for>>
/* Preview code for threshold model is similar; just update the body code to match. */
/* You MUST run this after every action if it is to be accurate, especially if you have actions that can modify initiative values. It is for this reason that I recommend a low prognosis value, so as to not cause your player's processor to melt. *//* Within the status pane, include this line: */
Ticks until next turn: <<print $actor[0].initiative>>
/* Creating a visual representation (such as in Becoming) will require skill in CSS and is beyond the scope of this help. */<<widget "levelcheck">>
<<set _keepGoing = true; $LevelUps = []>>
<<for _keepGoing === true>>
<<set _keepGoing = false>>
<<for _puppet range $puppets>>
<<if _puppet !== null>>
<<if _puppet.level < setup.LEVEL_CAP && _puppet.xp >= _puppet.XPtoNext()>>
<<run _puppet.level++; $LevelUps.push(_puppet)>>
<<set _keepGoing = true>>
/* This is needed if you want it to be possible for characters to level up multiple times within the same widget call. This ensures the whole check will be done again to check if the puppet's XP matches the requirement for the next level too. */
<</if>>
<</if>>
<</for>>
<</for>>
<</widget>>
<<widget "LevelUp">>
/* To implement this, you will need to enable the level up code detailed in the story JavaScript. */
<<set $B.subject = $args[0]>>
<<run getActor("subject")>>
<<set _num = 1>>
$B.subject.name has reached Level <<print $B.subject.level>>!<br/>
<<for _pn, _rate range $B.subject.growthRates>>
<<run $B.subject.stats[_pn].base += _rate>>
<<print _pn>> +<<print _rate>> = <b><<print $B.subject.getBase(_pn)>></b><br/>
<</for>>
/* You could also make the growth rate a formula that changes based on level. You could calculate it through a function similar to XPtoNext. */
/* Alternatively, use a stat table with unique returns for each level: */
<<set _return = $B.subject.StatTable(subject().level)>>
<<if typeof(_return) == 'string'>>
/* If only a string is returned, this assumes it is a stat name, and will increment the stat by a default amount (here, 1). */
<<run $B.subject.stats[_return].base += 1>>
<<print _return>> +1 = <<print $B.subject.getBase(_return)>>
<<elseif _return instanceof Action>>
/* If an action is returned, it will be added to the character's actions. */
<<run subject().actions.push(_action)>>
<<elseif typeof(_return) == 'object'>>
/* If an object is returned, behaves like growthRates. */
<<for _pn, _rate range _return>>
<<run $B.subject.stats[_pn].base += _rate>>
<<print _pn>> +<<print _rate>> = <b><<print $B.subject.getBase(_pn)>></b><br/>
<</for>>
<<else>>
<<print "ERROR in StatTable: undefined result">>
<</if>>
<br/>
<<if ($B.subject.level % _num) == 0>>
/* This is a handler for if you want to provide a special bonus at certain intervals. For instance, Dungeons & Dragons provides a stat point every 4 levels. This if case will trigger every _num levels. */
<</if>>
<br/>
<br/>
<center><<button "Continue" "Level Check">><</button>></center>
<</widget>>/* Presumably the player would get here by clicking a link from a menu passage of some kind. We'll assume this link set the $subject variable to the character in question. */
<<set $subject = $puppets[0]>>
<<set $subject.statPoints = 3>>
<<set _curr = new Map()>>
<<run _curr.set("points",$subject.statPoints)>>
<<for _key, _value range $subject.stats>>
<<run _curr.set(_key,_value.base)>>
<</for>>
<div id="stats">
<<include "statblock">>
</div>
/* This is for a regular stat point system. For a more complex point-buy system, you'd just use the "getStatCost" function and check stat points/XP against that instead of 0. */
<br/>Leaving this screen will make stat investments permanent!<br/>
<br/>
<<return>>/* Presumably you would have a link back to the menu or whatever here */Leveling up $subject.name!<br/>
<br/>
Current stat points: $subject.statPoints / <<print _curr.get("points")>><br/>
<<for _key, _value range $subject.stats>>
<<capture _key, _value>>
<span class="statname"><<print _key>>:</span> _value.base
<<if $subject.statPoints > 0>>
[<<link "+">>
<<replace "#stats">>
<<run _value.base++>>
<<run $subject.statPoints-->>
<<include "statblock">>
<</replace>>
<</link>>]
<</if>>
<<if $subject.statPoints > 0 and _value.base > _curr.get(_key)>> / <</if>>
<<if $subject.statPoints < _curr.get("points") and _value.base > _curr.get(_key)>>
[<<link "-">>
<<replace "#stats">>
<<run _value.base-->>
<<run $subject.statPoints++>>
<<include "statblock">>
<</replace>>
<</link>>]
<</if>>
<br/>
<</capture>>
<</for>><<if def $music>>
<div style="text-align:left">
<div class="marqueeWrapper">
<div style="margin-right:0.5em">♪</div>
<div id="marquee"><span>$music.title</span></div>
</div>
<span style="font-size:14px">by $music.author <<if $music.distributor !== null>>via $music.distributor<</if>></span>
<<if $music.license !== null>><br/>
<<if $music.license.toLowerCase() == "permission">>
Used with permission
<<elseif $music.license.toLowerCase() == "public domain">>
Public Domain
<<else>>
<<set _imgsrc = setup.ImagePath + "CC/" + $music.license + ".png">>
<<switch $music.license>>
<<case "CC BY">>
<<set _imglink = "https://creativecommons.org/licenses/by/4.0/">>
<<case "CC BY-NC">>
<<set _imglink = "https://creativecommons.org/licenses/by-nc/4.0/">>
<<case "CC BY-NC-ND">>
<<set _imglink = "https://creativecommons.org/licenses/by-nc-nd/4.0/">>
<<case "CC BY-NC-SA">>
<<set _imglink = "https://creativecommons.org/licenses/by-nc-sa/4.0/">>
<<case "CC BY-ND">>
<<set _imglink = "https://creativecommons.org/licenses/by-nd/4.0/">>
<<case "CC BY-SA">>
<<set _imglink = "https://creativecommons.org/licenses/by-sa/4.0/">>
<<default>>
<<set _imglink = "">>
<</switch>>
<a @href=_imglink target="_blank"><img @src=_imgsrc /></a>
<</if>>
<</if>>
<br/><span id="mutebutton"><<include "mute button">></span>
</div>
<</if>><<if !$muted>>
<<set _imgsrc = setup.ImagePath + "ui/volume_on_outline.png">>
<img @src=_imgsrc /> <span style="position:relative; bottom:4px"><<link "[M]">>
<<masteraudio mute>>
<<set $muted = true>>
<<replace "#mutebutton">><<include "mute button">><</replace>>
<</link>></span>
<<elseif $muted === true>>
<<set _imgsrc = setup.ImagePath + "ui/volume_off_outline.png">>
<img @src=_imgsrc /> <span style="position:relative; bottom:4px"><<link "[M]">>
<<masteraudio unmute>>
<<set $muted = false>>
<<replace "#mutebutton">><<include "mute button">><</replace>>
<</link>></span>
<</if>>/* Not currently implemented; too complex. Preserved for posterity. */
<<if $effects_to_remove <= 0>>
<<set $ready to true>>
<<goto "confirm phase">>
<</if>>
<span id="status">
<<include status>>
</span>
<span id="content">
<<backbtn>>
<<switch $action.name>>
<<case "Restoration">>
<<if $target[0].effects.length = 0>>
<<print $target[0].name+" has no effects! Go back.\n">>
<<else>>
<<set _count to 0>>
<<print $target[0].name+"\'s ailments:\n">>
<<for _i, _effect range $target[0].effects>>
<<if _effect.buff is false>>
<<capture _i>>
[[_effect.name|neutralize phase][$removed_effects.push(_i); $effects_to_remove -= 1]]
<</capture>>
<<else>>
<<set _count++>>
<</if>>
<</for>>
<<if _count eq $target[0].effects.length>>
<<print "...None! Go back.">>
<</if>>
<</if>>
<<case "Neutralize">>
<<if $target[0].effects.length = 0>>
<<print $target[0].name+" has no effects! Go back.\n">>
<<else>>
<<set _count to 0>>
<<print $target[0].name+"\'s buffs:\n">>
<<for _i, _effect range $target[0].effects>>
<<if _effect.buff is true>>
<<capture _i>>
[[_effect.name|neutralize phase][$removed_effects.push(_i); $effects_to_remove -= 1]]
<</capture>>
<<if _i < $target[0].effects.length>>
<<print ", ">>
<</if>>
<<else>>
<<set _count++>>
<</if>>
<</for>>
<<if _count eq $target[0].effects.length>>
<<print "...None! Go back.">>
<</if>>
<</if>>
<</switch>>
</span><span id="content" class="menu" style="padding:0">
<div id="bestiary">
<<include "bestiary-list">>
</div>
</span><div class="bestiary-header">
BESTIARY<br/>
<span class="smalltext">Click an enemy name to see detailed information.</span>
</div>
<div class="bestiary-container">
<<for _i, _enemy range $bestiary>>
/* if even (no remainder from modulo 2), go on right side (column 3), else go on left side (column 1) */
<<if (_i+1) % 2 === 0>>
<<set _col = 3>>
<<else>>
<<set _col = 1>>
<</if>>
<div class="bestiary-numbering" @style="'grid-column:'+_col">
<<print _enemy.bestiaryNo>>.
</div>
<div class="bestiary-entry" @style="'grid-column:'+(_col+1)">
<<if _enemy.encountered>>
<<set _name = $bestiary.fetch(_enemy.name).access().fullname>>
<<capture _enemy>>
<<link "_name">>
<<set _display = clone(_enemy); _stScreen = 1>>
<<replace "#bestiary" t8n>><<include "bestiary-display">><</replace>>
<</link>>
<</capture>>
<<else>>
????
<</if>>
</div>
<</for>>
</div><div class="bestiary-display">
<div id="toggle" class="bestiary-name">
<<set _name = _display.access().fullname>>
<<link _name>>
<<unset _display>>
<<replace "#bestiary" t8n>><<include "bestiary-list">><</replace>>
<</link>>
<div class="bestiary-display-numbering">
#<<print _display.bestiaryNo>><br/>
<span id="lastentry">
<<link "⬅">>
<<set _display = clone($bestiary.lastEntry(_display.bestiaryNo-1))>>
<<replace "#bestiary" t8n>><<include "bestiary-display">><</replace>>
<</link>>
</span>
<span id="nextentry">
<<link "⮕">>
<<set _display = clone($bestiary.nextEntry(_display.bestiaryNo-1))>>
<<replace "#bestiary" t8n>><<include "bestiary-display">><</replace>>
<</link>>
</span>
</div>
</div>
<div class="bestiary-detail">
<div class="monospace bestiary-form-container">
<<if _display.altsKnown>>
<span class="bestiary-form-entry">
<<set _formName = (setup.enemyData[_display.name].bestiaryName || _display.name)>>
<<if _display.altSkin === null>>
<b><<print _formName>></b>
<<else>>
<<link "_formName">>
<<set _display.altSkin = null>>
<<replace "#bestiary">><<include "bestiary-display">><</replace>>
<</link>>
<</if>>
</span>
<<for _pn, _v range _display.altsKnown>>
<<if _v === true>>
<span class="bestiary-form-entry">
<<set _formName = (setup.enemyData[_pn].bestiaryName || _pn)>>
<<if _display.altSkin == _pn>>
<b><<print _formName>></b>
<<else>>
<<capture _pn>>
<<link "_formName">>
<<set _display.altSkin = _pn>>
<<replace "#bestiary">><<include "bestiary-display">><</replace>>
<</link>>
<</capture>>
<</if>>
</span>
<</if>>
<</for>>
<<else>>
<</if>>
</div>
<div class="bestiary-detail-container">
<div id="bestiary-infoblock" class="monospace">
<<include "bestiary infoblock">>
</div>
<div class="bestiary-desc">
<<print _display.desc>>
</div>
</div>
<div class="abilities-header">ABILITIES</div>
<div>
<<print _display.abilityInfo>>
</div>
<div class="bestiary-defeated-count">
Defeated: <<print _display.defeated>>
</div>
</div>
</div><div style="position:relative">
<<switch setup.STATUS_SCREENS.bestiary[_stScreen-1]>>
<<case "elements">>
<<set _title = "elemental affinities">>
<<case "ailments">>
<<set _title = "ailment tolerances">>
<<default>>
<<set _title = setup.STATUS_SCREENS.bestiary[_stScreen-1]>>
<</switch>>
<div class="menuactor-special-title">_title</div>
<<if setup.STATUS_SCREENS.bestiary.length > 1>>
<div class="cyclebuttons" style="right:0">
<<link "<">>
<<set _stScreen-->>
<<if _stScreen < 1>>
<<set _stScreen = setup.STATUS_SCREENS.bestiary.length>>
<</if>>
<<replace "#bestiary-infoblock">><<include "bestiary infoblock">><</replace>>
<</link>>
<<link ">">>
<<set _stScreen++>>
<<if _stScreen > setup.STATUS_SCREENS.bestiary.length>>
<<set _stScreen = 1>>
<</if>>
<<replace "#bestiary-infoblock">><<include "bestiary infoblock">><</replace>>
<</link>>
</div>
<</if>>
</div>
<<switch setup.STATUS_SCREENS.bestiary[_stScreen-1]>>
<<case "stats">>
<div style="display:grid; grid-auto-rows:auto; grid-template-columns: 75% 25%; width:75%">
<div>HP:</div>
<div style="text-align:right">
<<print _display.get("hp")>>
</div>
<<for _k, _v range setup.statInfo>>
<<if !setup.hiddenStats.includes(_k)>>
<div style="text-transform:uppercase"><<print _k>>:</div>
<div style="text-align:right">
<<print _display.get(_k)>>
</div>
<</if>>
<</for>>
</div>
<<case "rewards">>
<div style="display:grid; grid-template-columns: 50% 50%;">
<div>
XP: <<print _display.get("XP")>>
</div>
<div>
GP: <<print _display.get("GP")>>
</div>
</div><br/>
<center>ITEM DROPS</center>
<<set _itemTable = _display.itemDrops>>
<<if _itemTable.length > 0>>
<<for _item, _chance range _itemTable>>
<<print _item>><span style="float:right; font-weight:normal; margin-left:1em"><<print _chance>>%<br/>
<</for>>
<<else>>
<span style="color:gray; font-style:italic; font-weight:normal; font-family:Helmet,sans-serif;">No items.</span>
<</if>>
<<case "elements">>
<<for _k, _v range _display.access().elements>>
<span class="statname"><<print _k>></span>
<span class="elementdisplay">
<<if _display.statsKnown[_k] === true>>
<<if setup.SOAK>><<soak _v>> /<</if>><div style="display: inline-block; min-width:45px; text-align:right"><<res _v>></div>
<<else>>
<<if setup.SOAK>>?? /<</if>><div style="display: inline-block; min-width:45px; text-align:right">???%</div>
<</if>>
</span>
<br/>
<</for>>
<<case "ailments">>
<div style="width:75%; font-weight:bold">
<<for _k, _v range _display.access().tolerances>>
<<if _v.current != 0>>
<<if _display.tolerancesKnown[_k] === true>>
<<print _k>>
<<tol _v>>
<<else>>
<div class="tolerance" style="text-align:left">????</div>
<</if>>
<br/>
<</if>>
<</for>>
</div>
<</switch>><div id="content" class="menu"><<nobr>>
<div id="puppets" class="actors grid">
<<include "formation puppets">>
</div>
<</nobr>>
Click on a character to select them, then click on another space to move or swap.
</div><div class="leaderlabel formation-label">Front Row</div>
<div class="frontrowlabel formation-label">Middle Row</div>
<div class="backrowlabel formation-label">Back Row</div>
<<for _i, _puppet range $puppets>>
<<set _puppet_class = "actor grid">>
<div @class="_puppet_class" @id="'p'+_i">
<<if _puppet !== null>>
<div style="display: flex; justify-content: center; align-items: center; height: -webkit-fill-available">
<span @id="'pname'+_i">
<<if ndef _s>>
<<capture _i, _puppet>>
<<link "_puppet.name">>
<<set _s = _i>>
<<replace "#puppets">><<include "formation puppets">><</replace>>
<</link>>
<</capture>>
<<elseif $puppets[_s] !== _puppet>>
<<capture _i, _puppet>>
<<link "_puppet.name">>
<<set _holder = _puppet>>
<<set $puppets[_i] = $puppets[_s]; $puppets[_s] = _holder>>
<<unset _s>>
<<replace "#puppets">><<include "formation puppets">><</replace>>
<</link>>
<</capture>>
<<else>>
<span class="actorname" style="font-weight:bold">_puppet.name</span>
<</if>>
</span>
</div>
<<elseif _puppet == null>>
<<if def _s>>
<div style="display: flex; justify-content: center; align-items: center; height: -webkit-fill-available">
<<link "[MOVE]">>
<<set _holder = _puppet>>
<<set $puppets[_i] = $puppets[_s]; $puppets[_s] = _holder>>
<<unset _s>>
<<replace "#puppets">><<include "formation puppets">><</replace>>
<</link>>
</div>
<</if>>
<</if>>
</div>
<</for>>
<<timed 0s>>
<<script>>
$("#puppets.actors.grid").css({
"grid-template-columns": `auto repeat(${setup.ROW_SIZE},1fr)`
});
<</script>>
<</timed>><<widget "actorDisplay">>
/* Standard display code for a single character. By default, shows their name and stats on the left, and their equipment on the right. Modifiers can be used to change specific parts.
ARG 0 = character to be displayed
ARG 1 = special case modifier [optional]
Be VERY CAREFUL about labeling your temporary variables. This widget uses a lot of loops, and errors can occur if they overwrite temporary variables used elsewhere. */
<<if ndef $args[0]>> /* error handler */
<<print "ERROR: No puppet passed to actorDisplay">>
<<run console.log("ERROR in actorDisplay: No puppet passed")>>
<<else>>
<<set _puppet = $args[0]>>
<<set _displayModel = $args[1]>>
<<set _style = "">>
<<if def _displayModel && _displayModel !== "standard">>
<<set _classMod = " small-display">>
<<else>>
<<set _classMod = "">>
<</if>>
<<if ndef _i>>
<<set _i = 0>>
<</if>>
<<if _puppet !== null>>
<div @class="'menuactor'+_classMod" @id="_i">
/* container for the entire actor box */
<<switch _displayModel>>
<<default>>
/* Default display makes room for a portrait on the left-hand side, if they're enabled. */
<<if setup.PORTRAITS === true>>
<div class="portrait">[img[setup.ImagePath+_p.portrait]]</div>
<<set _style = "left:96px">>
<</if>>
<</switch>>
<div @class="'menuactor-nameblock'+_classMod" @style=_style>
/* container for the left-hand side of the box, typically containing the puppet's name */
<div class="menuactor-name" id="toggle">
/* Container for the puppet's name. This is a point of interaction in most menus, so there is a switch here for different menu functionalities. */
<<actorNameLink>>
</div> /* close name display */
<<switch _displayModel>>
<<case "inventory" "decurse" "equipment">>
/* In these menus, the character's stats are displayed below their name. */
<div @class="'menuactor-stats monospace'+_classMod">
<<for _k, _v range _puppet.stats>>
<<include "menu stat display">>
<</for>>
</div>
<<default>>
/* In the default display, the character's level is displayed below their name. */
<div class="menuactor-level">LEVEL _puppet.level</div>
<</switch>>
</div> /* close name block */
<<actorRightDisplay>>
</div> /* close actor box */
<</if>>
<</if>> /* close error handler if */
<</widget>><<widget "actorNameLink">>
/* Switch for different functionalities tied to the character's name in actorDisplay. */
<<switch _displayModel>>
<<case "inventory">>
/* On the inventory screen, characters may be displayed when EQUIPPING or USING items. In both cases, the character name is clicked to perform the relevant function. The inventory screen must be updated afterwards to reflect the change. */
<<switch _event>>
<<case "equip">>
<<capture _puppet>>
<<link _puppet.name>>
<<run _puppet.equip(_display)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</capture>>
<<case "use">>
<<capture _puppet>>
<<link _puppet.name>>
<<run _display.onUse(_puppet)>>
<<unset _display>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</capture>>
<<default>>
<<print _puppet.name>>
<</switch>>
<<case "decurse">>
/* In the decurse menu, the name has no special functionality. Just display it normally. */
<<print _puppet.name>>
<<case "equipment">>
/* In the equipment menu, the character name serves two functionalities: When the full party is displayed, clicking it will select the character; if a character is selected, clicking it will deselect them and return to the full party display. */
<<if def _s>> /* if selector already defined, deselect and refresh screen */
<<link _puppet.name>>
<<unset _s>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<<replace "#equipment-list">><<include "equipment list">><</replace>>
<</link>>
<<else>> /* if no one selected, select this character and refresh screen */
<<capture _i>>
<<link _puppet.name>>
<<if def _s>>
<<if _s != _i>>
<<set _s = _i>>
<<else>>
<<unset _s>>
<</if>>
<<else>>
<<set _s = _i>>
<</if>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<<replace "#equipment-list">><<include "equipment list">><</replace>>
<</link>>
<</capture>>
<</if>>
<<default>>
/* In the default display, the character name serves two functionalities: When the full party is displayed, clicking it will select the character; if a character is selected, clicking it will deselect them and return to the full party display. */
<<if def _display>>
/* If _display exists, a character has already been selected. Clicking the name again will deselect the character. */
<span id="toggle">
<<link "_puppet.name">>
<<unset _display>>
<<replace "#partydisplay">><<include "status party display">><</replace>>
<</link>>
</span>
<<else>>
/* If no character is selected, clicking the name will select this character. */
<span @id=_i>
<<capture _puppet>>
<<link "_puppet.name">>
<<set _display = _puppet>>
<<set _stScreen = 1>>
<<replace "#partydisplay">><<include "status party display">><</replace>>
<</link>>
<</capture>>
</span>
<</if>>
<</switch>>
<</widget>><<widget "actorRightDisplay">>
/* Branch for determining what is displayed on the right-hand side of the actor display. */
<<if (_displayModel == "inventory" && _event == "use") || (ndef _displayModel || _displayModel == "standard")>>
/* By default or if we're using an item on the inventory screen, we want to display HP and MP on the right-hand side. */
<<set _id = 'hp'+_i>>
<<run _HPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.PLAYER_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<<set _id = 'mp'+_i>>
<<run _MPmeters.push(_id)>>
<<newmeter _id 1>>
<<colors setup.MP_BAR_COLOR>>
<<animation false>>
<<sizing 100% 0.5em>>
<</newmeter>>
<div @class="'menuactor-hpmpblock'+_classMod">
<div class="hpmp">
HP: <span style="float:right">
<div class="hpmpvalue">_puppet.hp</div>/<div class="hpmpvalue">_puppet.maxhp</div>
</span>
</div>
<<showmeter _HPmeters[puppets().indexOf(_puppet)] `_puppet.hp / _puppet.maxhp`>>
<<if setup.SHOW_MP === true>>
<div class="hpmp">
MP: <span style="float:right">
<div class="hpmpvalue">_puppet.en</div>/<div class="hpmpvalue">_puppet.maxen</div>
</span>
</div>
<<showmeter _MPmeters[puppets().indexOf(_puppet)] `_puppet.en / _puppet.maxen`>>
<</if>>
</div>
<<else>>
/* In all other cases, display equipment on the right-hand side. */
<div class="menuactor-equipment small-display">
<<for _slot, _v range _puppet.equipment>>
<div class="menuactor-equipment-slot">_slot</div>
<<if _v instanceof Array>>
<<for _subslot, _subitem range _v>>
<<if _subslot > 0>>
<div class="menuactor-equipment-slot">_slot</div>
<</if>>
<<equipSlotDisplay _subitem>>
<</for>>
<<else>>
<<equipSlotDisplay _v>>
<</if>>
<</for>>
</div>
<</if>>
<<if ndef _displayModel || _displayModel == "standard">>
/* In the standard display, an additional XP block is displayed to the right of the previous block. */
<div class="menuactor-xpblock">
<div style="font-weight:bold">Experience</div>
<div style="float:right">_puppet.xp</div>
<br/>
<div style="font-weight:bold">To next level</div>
<<set _toNext = _puppet.XPtoNext()>>
<div style="float:right"><<if typeof(_toNext) == 'number'>><<print (_toNext-_puppet.xp)>><<else>><<print _toNext>><</if>></div>
</div>
<</if>>
<</widget>><<widget "equipSlotDisplay">>
/* Standardized display for a single instance of character equipment. By default, this displays below the slot name itself. Assume called within actorDisplay, and uses _displayModel for modular functionality.
ARG 0 = equipped item
*/
<<if ndef $args[0]>> /* error handler */
<<print "ERROR: No item passed to equipSlotDisplay">>
<<run console.log("ERROR in equipSlotDisplay: no item passed")>>
<<else>>
<<set _item = $args[0]>>
<<if _item === null>><span class="menuactor-equipment-name"> </span> /* if slot empty, display blank space */
<<else>>
<div class="menuactor-equipment-name">_item.name<br/>
<span class="actdesc">_item.info</span>
</div>
<span class="unequip-button">
<<switch _displayModel>>
/* Unequip button displayed here. By default, this is an "[X]" link at the far right of the item name. */
<<case "inventory">>
/* no unequip feature; display nothing */
<<case "decurse">>
/* In Decurse, only display unequip if the item is sticky and player can pay the cost. Deduct the cost and refresh the shop screen after click. */
<<if $currency >= _decurseCost && !_puppet.lockEquipment && _item.sticky>>
<<capture _puppet, _slot, _subslot>>
<<link "[X]">>
<<run _puppet.unequip(_slot,_subslot,{unsticky:true})>>
<<set $currency -= _decurseCost>>
<<update>>
<<replace "#business-area" t8n>><<include "decurse-remove">><</replace>>
<</link>>
<</capture>>
<</if>>
<<default>>
/* Default is party equipment screen. Only display unequip if the item is not sticky and the puppet does not have locked equipment. Remember to pass the subslot index too for subslot equipment. */
<<if !_puppet.lockEquipment && !_item.sticky>>
<<capture _puppet, _slot, _subslot>>
<<link "[X]">>
<<run _puppet.unequip(_slot,_subslot)>>
<<replace "#equipment-list">><<include "equipment list">><</replace>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<</link>>
<</capture>>
<</if>>
<</switch>>
</span>
<</if>>
<</if>> /* end error handler if */
<</widget>><<widget "unequipAll">>
<<link "Unequip All">>
<<for _puppet range puppets().concat($Reserve_Puppets).filter(function(p){ return !p.lockEquipment; })>>
<<for _k, _v range _puppet.equipment>>
<<if _v.sticky>>
<<set _stop = true>>
<<break>>
<</if>>
<</for>>
<<if !_stop>>
<<run _puppet.unequipAll()>>
<</if>>
<</for>>
<<goto `passage()`>>
<</link>>
<</widget>><span id="status">
<span class="menunav monospace">
[A] ↑<br/>
[D] ↓
</span>
<<for _n, _option range setup.MENU_OPTIONS>>
<<if _n != $menu_screen>>
<<capture _n, _option>>
<span @id="'menu'+_n">
<<link _option>>
<<set $menu_screen = _n>>
<<print '<<goto \"Menu: '+_option+'\">>'>>
<</link>></span><</capture>>
<<else>>
<b>_option</b>
<</if>>
<br/>
<</for>>
<span id="menu-return"><<longreturn>></span>
<div class="GPdisplay monospace"><<print setup.CURRENCY_NAME>>: $currency</div>
</span><span id="content" class="menu"><<nobr>>
<div id="partydisplay">
<<include "status party display">>
</div>
<</nobr>>
</span><<if !setup.hiddenStats.includes(_k)>>
<<if def setup.statInfo[_k]>>
<<set _class = "tooltip">>
<<else>>
<<set _class = "">>
<</if>>
<div class="menuactor-statname"><span @class=_class>_k<<if def setup.statInfo[_k]>><span class="tooltiptext"><<print setup.statInfo[_k]>></span><</if>></span></div>
<div class="menuactor-statvalue">_v.base</div>
<<if _v.bonus != 0>>
<div class="menuactor-statmod">
<<if _v.bonus > 0>>
@@.green;<<print "\+"+_v.bonus>>@@
<<elseif _v.bonus < 0>>
@@.stat-lowered;<<print "-"+_v.bonus>>@@
<</if>>
</div>
<</if>>
<</if>><div style="position:relative">
<<switch setup.STATUS_SCREENS.menu[_stScreen-1]>>
<<case "elements">>
<<set _title = "elemental affinities">>
<<case "ailments">>
<<set _title = "ailment tolerances">>
<<default>>
<<set _title = setup.STATUS_SCREENS.menu[_stScreen-1]>>
<</switch>>
<div class="menuactor-special-title">_title</div>
<<if setup.STATUS_SCREENS.menu.length > 1>>
<div class="cyclebuttons">
<span id="statusback">
<<link "<">>
<<set _stScreen-->>
<<if _stScreen < 1>>
<<set _stScreen = setup.STATUS_SCREENS.menu.length>>
<</if>>
<<replace "#menuactor-special">><<include "status special detail">><</replace>>
<</link>>
</span>
<span id="statusforward">
<<link ">">>
<<set _stScreen++>>
<<if _stScreen > setup.STATUS_SCREENS.menu.length>>
<<set _stScreen = 1>>
<</if>>
<<replace "#menuactor-special">><<include "status special detail">><</replace>>
<</link>>
</span>
</div>
<</if>>
</div>
<<switch setup.STATUS_SCREENS.menu[_stScreen-1]>>
<<case "equipment">>
<div style="margin-left:1em;">
<<for _k, _v range _display.equipment>>
<div class="menuactor-equipment-slot">_k</div>
<<if _v instanceof Array>>
<<for _index, _slot range _v>>
<<if _index > 0>>
<div class="menuactor-equipment-slot">_k</div>
<</if>>
<<if _slot === null>><span class="menuactor-equipment-name"> </span>
<<else>>
<div class="menuactor-equipment-name">_slot.name</div>
<</if>>
<</for>>
<<else>>
<<if _v === null>><span class="menuactor-equipment-name"> </span>
<<else>>
<div class="menuactor-equipment-name">_v.name</div>
<</if>>
<</if>>
<</for>>
</div>
<<case "elements">>
<div style="margin-left:1em; width:75%; font-weight:bold">
<<for _k, _v range _display.elements>>
<span class="statname"><<print _k>></span>
<span class="elementdisplay monospace"><<if setup.SOAK>><<soak _v>> /<</if>><div style="display: inline-block; min-width:45px; text-align:right"><<res _v>></div></span>
<br/>
<</for>>
</div>
<<case "ailments">>
<div style="margin-left:1em; width:75%; font-weight:bold">
<<for _k, _v range _display.tolerances>>
<<if _v.current != 0>>
<<print _k>>
<<tol _v>>
<br/>
<</if>>
<</for>>
</div>
<</switch>><<if def _display>>
<<actorDisplay _display>>
<div class="menuactor-detail">
<<if def setup.STATUS_SCREENS.menu && setup.STATUS_SCREENS.menu.length > 0>>
<<set _style = "">>
<<else>>
<<set _style = "border-right: none">>
<</if>>
<div class="menuactor-stats monospace" @style=_style>
<<for _k, _v range _display.stats>>
<<include "menu stat display">>
<</for>>
<<if def _display.HPregen && _display.HPregen !== null>>
<div class="menuactor-statname">HP regen</div>
<div class="menuactor-statvalue"><<print _display.HPregenFlat>> / <<print _display.HPregenPercent*100>>%</div>
<</if>>
</div>
<<if def setup.STATUS_SCREENS.menu && setup.STATUS_SCREENS.menu.length > 0>>
<div id="menuactor-special">
<<include "status special detail">>
</div>
<</if>>
</div>
<br/>
<<if _display.crisis instanceof Array>>
<div class="abilities-header" style="font-weight:bold">CRISIS</div>
<br/>
<<for _action range _display.crisis>>
<<actionInfo _action _display "full">>
<</for>>
<br/>
<</if>>
<div class="abilities-header">ABILITIES</div>
<br/>
<div id="menuActionList">
<<actionlist _display>>
</div>
<<elseif ndef _display>>
<<set _HPmeters = []>>
<<set _MPmeters = []>>
<<for _i, _p range puppets()>>
<<actorDisplay _p>>
<</for>>
<</if>><span id="content" class="menu"><<nobr>>
<<set _filter = "all">>
<div id="itemdisplay">
<<include "inventory item display">>
</div>
<div id="itemlist">
<<include "inventory item list">>
</div>
<</nobr>>
</span><div style="display:flex; justify-content:space-evenly;">
<<if _filter == "all">>
<b>All</b>
<<else>>
<<link "All">>
<<set _filter = "all">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<<if _filter == "usable">>
<b>Usable</b>
<<else>>
<<link "Usable">>
<<set _filter = "usable">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
<<if _filter == "equipment">>
<b>Equipment</b>
<<else>>
<<link "Equipment">>
<<set _filter = "equipment">>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</link>>
<</if>>
</div>
<br/>
<div class="itemcontainer">
<<set _p = 2>>
<<for _name, _item range $inventory>>
<<if _filter == "all" || (_filter == "usable" && _item.usable.includes("inmenu")) || (_filter == "equipment" && _item.equippable)>>
<<if _p == 1>>
<<set _p = 2>>
<<elseif _p == 2>>
<<set _p = 1>>
<</if>>
<div @class="'item'+_p">
<b>
<<capture _item>>
<<link _item.name>>
<<set _display = _item>>
<<replace "#itemdisplay">><<include "inventory item display">><</replace>>
<</link>>
<</capture>>
</b> <span class="itemstock">(_item.stock)</span>
</div>
<</if>>
<</for>>
</div><<if ndef _display || _display === null>>
<div style="text-align: center; line-height: 70px; color: gray">Click on an item to see info</div>
<<else>>
<b>_display.name</b><br/>
<<if def _display.equippable>><i style="font-size:12px"><<print _display.equippable.slot>></i><br/><</if>>
_display.info<br/>
<div class="actdesc">_display.desc</div>
<<if _display.usable.includes('inmenu') || _display.equippable>>
<div style="display: flex; justify-content: space-evenly">
<<set _b = 0>>
<<if _display.usable.includes('inmenu') && _display.stock > 0>>
<<set _b++>>
<span @id="'button'+_b">
<span id="usebutton">
<<include "inventory use button">>
</span>
</span>
<</if>>
<<if _display.equippable && _display.stock > 0>>
<<set _b++>>
<span @id="'button'+_b">
<span id="equipbutton">
<<include "equip button">>
</span>
</span>
<</if>>
</div>
<</if>>
<</if>><<button "EQUIP">>
<<set _event = "equip">>
<<if document.getElementById("usebutton")>><<replace "#usebutton">><<include "inventory use button">><</replace>><</if>>
<<if document.getElementById("equipbutton")>><<replace "#equipbutton">><<include "inventory cancel button">><</replace>><</if>>
<<replace "#itemlist">><<include "inventory puppets">><</replace>>
<</button>>
<<if _b == 1>>
<span class="monospace" style="font-size:85%">[Q]</span>
<<elseif _b == 2>>
<span class="monospace" style="font-size:85%">[W]</span>
<</if>><<button "USE">>
<<set _event = "use">>
<<if document.getElementById("usebutton")>><<replace "#usebutton">><<include "inventory cancel button">><</replace>><</if>>
<<if document.getElementById("equipbutton")>><<replace "#equipbutton">><<include "equip button">><</replace>><</if>>
<<replace "#itemlist">><<include "inventory puppets">><</replace>>
<</button>>
<span class="monospace" style="font-size:85%">[Q]</span><span id="cancelbutton">
<<button "CANCEL">>
<<if document.getElementById("usebutton")>><<replace "#usebutton">><<include "inventory use button">><</replace>><</if>>
<<if document.getElementById("equipbutton")>><<replace "#equipbutton">><<include "equip button">><</replace>><</if>>
<<replace "#itemlist">><<include "inventory item list">><</replace>>
<</button>>
</span>
<span class="monospace" style="font-size:85%">[R]</span><<set _HPmeters = []>>
<<set _MPmeters = []>>
<<for _i, _p range puppets().filter(function(p) { return temporary().event != "equip" || (!p.lockEquipment && p.checkRestriction(temporary().display)); })>>
<<actorDisplay _p "inventory">>
<</for>><span id="content" class="menu"><center style="font-weight:bold">ACTIVE PUPPETS</center>
<<nobr>>
<div class="actors" id="puppets">
<<include "party manager puppets">>
</div>
<</nobr>>
<center style="font-weight:bold">RESERVE PUPPETS</center>
<<nobr>>
<div class="actors" id="reserve">
<<include "party manager reserve">>
</div>
<</nobr>>
<<unequipAll>>
</span><<for _i, _puppet range $puppets>>
<<if _puppet !== null>>
<div class="actor" @id="_i" style="min-width:150px">
<center>
<<capture _i>>
<<link _puppet.name>>
<<if def _s>>
<<run $('#' + _s).removeClass("selected")>>
<<if _s != _i>>
<<set _s = _i>>
<<run $('#' + _s).addClass("selected")>>
<<else>>
<<unset _s>>
<</if>>
<<else>>
<<set _s = _i>>
<<run $('#' + _i).addClass("selected")>>
<</if>>
<<run console.log("i = "+_i); console.log("s = "+_s)>>
<<replace "#reserve">><<include "party manager reserve">><</replace>>
<</link>>
<</capture>>
</center><br/>
<<for _k, _v range _puppet.stats>>
<span class="statname"><<print _k>>:</span>
<<statOOB _k _puppet>>
<br/>
<</for>>
</div>
<</if>>
<</for>><<for _j, _puppet range $Reserve_Puppets>>
<div class="actor" style="min-width:150px">
<center>
<<if def _s>>
<<capture _puppet, _j>>
<<link _puppet.name>>
<<run $Reserve_Puppets[_j] = $puppets[_s]>>
<<set $puppets[_s] = _puppet>>
<<unset _s>>
<<replace "#puppets">><<include "party manager puppets">><</replace>>
<<replace "#reserve">><<include "party manager reserve">><</replace>>
<</link>>
<</capture>>
<<else>>
_puppet.name
<</if>>
</center><br/>
<<for _k, _v range _puppet.stats>>
<span class="statname"><<print _k>>:</span>
<<statOOB _k _puppet>>
<br/>
<</for>>
</div>
<</for>><span id="content" class="menu"><<nobr>>
<div id="puppets">
<<include "equip manager puppets">>
</div>
<div id="equipment-list">
<<include "equipment list">>
</div>
<</nobr>>
</span><<if def _s>>
<<set _i = "null">>
<<actorDisplay $puppets[_s] "equipment">>
<<else>>
<<for _i, _p range $puppets>>
<<actorDisplay _p "equipment">>
<</for>>
<</if>><<if def _s && !$puppets[_s].lockEquipment>>
<div style="line-height:1.4">
<<set _count = 0>>
<<for _k, _v range $inventory>>
<<if _v.equippable && _v.stock > 0>>
<br/>
<<set _count++>>
<<if (def _s && $puppets[_s].checkRestriction(_v)) || ndef _s>>
<div @id="_k">
<b>
<<if def _s>>
<<capture _v>>
<<link _v.name>>
<<run $puppets[_s].equip(_v)>>
<<replace "#puppets">><<include "equip manager puppets">><</replace>>
<<replace "#equipment-list">><<include "equipment list">><</replace>>
<</link>>
<</capture>>
<<else>>
<<print _k>>
<</if>>
</b>
<span style="float:right">(_v.stock)</span><br/>
<i style="font-size:12px"><<print _v.equippable.slot>></i><br/>
<<if _v.equippable.restrictedTo.length > 0>>
<div style="font-size:12px; font-style:italic">Restriction: <<for _char range _v.equippable.restrictedTo>><<print _char>><</for>></div>
<</if>>
<<print _v.desc>><br/>
<span class="actdesc"><<print _v.info>></span>
</div>
<</if>>
<</if>>
<</for>>
<<if _count == 0>>
You don't have any equipment.
<</if>>
</div>
<<elseif def _s && $puppets[_s].lockEquipment>>
<i>This character's equipment can't be changed.</i>
<<else>>
<<unequipAll>>
<</if>>/* Old passage for the "Procure" skill, which allowed Rogue and Witch to expend Energy to buy items in battle. It was dummied out before the JavaScript overhaul, so it is unlikely to work in the current version without tweaking.
/* Procure costs use the "cost" attribute of Item objects, defined in the database JS. */
<<set $target = [null,null]>>
<span id="content">
<<backbtn>>
<<for _key, _item range $inventory>>
<b>
<<if $subject[0].en >= _item.value>>
<<capture _key, _item>>
<<set _linktext = "Procure "+_key>>
[[_linktext|confirm phase][$item_to_procure = _key; $action.cost = _item.value]]
<</capture>>
<<else>>
<del>_key</del>
<</if>>
</b>
(Cost: <<print _item.value>>)<br/>
<</for>>
</span><<set _XPBars = []; _animationTime = "1s">>
<center><div style="font-size:20pt; font-weight:bold; margin-top:1em; margin-bottom:1em">+<<print $B.XPreward>> XP</div></center>
<div class="actors">
<<for _i, _puppet range puppets()>>
<<set _id = 'p'+_i>>
<<run _XPBars.push(_id)>>
<<set _value = Math.clamp((_puppet.xp - _puppet.XPtoNext(_puppet.level - 1)) / (_puppet.XPtoNext() - _puppet.XPtoNext(_puppet.level - 1)),0,1)>>
<<newmeter _id _value>>
<<colors cyan cyan black>>
<<animation _animationTime linear>>
<<sizing 100%>>
<</newmeter>>
<div class="actor victory">
<center style="text-transform:uppercase; font-weight:bold">_puppet.name</center>
LEVEL <span @id="'lv'+_i">_puppet.level</span>
<<showmeter _XPBars[_i] _value>>
<center @id="'lvlupmsg'+_i"> </center>
</div>
<<set _multiplier = 1>>
<<set _puppet.xp += Math.round($B.XPreward * _multiplier)>>
<<set _value = Math.clamp((_puppet.xp - _puppet.XPtoNext(_puppet.level - 1)) / (_puppet.XPtoNext() - _puppet.XPtoNext(_puppet.level - 1)),0,1)>>
<<updatemeter _XPBars[_i] _value>>
<</for>>
</div>
<<set $currency += $B.moneyReward>>
<center style="margin-top:1em;"><div style="display:inline-block; width:50%; font-size:20pt; text-align:left"><<print setup.CURRENCY_NAME>> <span style="float:right">$B.moneyReward</span></div></center>
<<set _itemDrops = []>>
<<for _enemy range $B.kills>>
<<for _item, _chance range _enemy.itemDrops>>
/* Assumes itemDrops is an object with property names of items corresponding to a number between 1 and 100 equal to the % chance of the item being dropped. */
<<if typeof(_item) == 'string' && typeof(_chance) == 'number'>>
<<set _r = random(1,100)>>
<<if _r <= _chance>>
<<run inv().addItem(_item); _itemDrops.push(_item)>>
<</if>>
<</if>>
<</for>>
<</for>>
<<if _itemDrops.length > 0>>
<center style="margin-top:1em;">
<div style="font-size:20pt; font-weight:bold">DROPS:</div>
<span class="itembox" style="width:30%">
<<for _item range _itemDrops>>
<b><<print _item>></b><br/>
<</for>>
</span>
</center>
<</if>>
<<timed _animationTime>>
<<for _i, _puppet range puppets()>>
<<if _puppet.xp >= _puppet.XPtoNext()>>
<<set _id = "#lv"+_i>>
<<replace _id>><span style="font-weight:bold"><<print (_puppet.level+1)>></span><</replace>>
<<set _id = "#lvlupmsg"+_i>>
<<replace _id>><span style="font-weight:bold; color:cyan">LEVEL UP!</span><</replace>>
<</if>>
<</for>>
<<levelcheck>>
<<timed 3s>>
<<goto "Level Check">>
<</timed>>
<</timed>><<if $LevelUps.length > 0>>
<<set _p = $LevelUps.shift()>>
<center><<LevelUp _p>></center>
<<else>>
<<endofbattle>>
<<goto $B.destination>>
<</if>>You lost! You can add a special defeat message here.
>[[Retry this battle|Preparation]]
>[[Return|Start]]
<<endofbattle>>/* Custom PassageDone code, appended to the core passage. *//* Custom PassageFooter code, appended to the core passage. */
<<if tags().includes("info")>>
<hr/>
<center><a href="#navbar">BACK TO TOP</a></center>
<</if>>/* Custom PassageHeader code, appended to the core passage. */
<<if tags().includes("text")>>
<<include "navbar">>
<</if>>/* Custom PassageReady code, appended to the core passage. */Made in <a href="https://anotherrpgenthusiast.itch.io/another-rpg-engine">Another RPG Engine</a>
/* Please keep this link if you make games in the engine! */<<nobr>>
<<if $debug>>
<<if $inbattle>>
<<for _i, _puppet range $puppets>>
<<set _linkText = "Puppet "+_i+" Crisis">>
<<capture _puppet>>
<<link _linkText>>
<<set _puppet.crisisPoints = 100>>
<<goto "Battle!">>
<</link>>
<</capture>><br/>
<<set _linkText = "Kill Puppet "+_i>>
<<capture _puppet>>
<<link _linkText>>
<<set _puppet.dead = true>>
<<goto "Battle!">>
<</link>>
<</capture>><br/>
<</for>>
<</if>>
<</if>>
<</nobr>>
<<include "hotkey info">>
<<include "music info">>/* Called at the start of actorlist. Use this for any modifications you want to make to the default display, such as reversing the order of a party.
The container class for enemies is _enemiesClass, and the container class for puppets is _puppetsClass.
Add your class to the variable with a leading space, as the variable is one string that will contain all the classes.
*/
<<if $scenario is "reverse display">>
<<set _enemiesClass += " reverse">>
<</if>>/* Special scenarios that interrupt the normal display, such as the Crystal Gems defending Steven. By default, this has margins both above and below to make it its own block (see story stylesheet). You may want to adjust this if you plan to make an interruption at a different point. */
/* You may wish to make separate passages for each of these, but the example will be stored here. */
<<if $scenario == "su1" and $B.event == false and $B.turn == "player" and ($B.target instanceof Enemy)>>
/* When vs. the Crystal Gems, the Gems will take hits for Steven if you try to attack him. */
<<if target().name == "Steven">>
<<if $enemies[1].dead is false>>
/* Garnet is the first to defend him */
@@.interruption;
$enemies[1].name's head snaps towards you and she jumps in the way of your attack, leaving Steven unharmed.@@
<<set _t to 1>>
<<elseif $enemies[0].dead is false>>
/* If Garnet is poofed, Pearl plays defense */
@@.interruption;
$enemies[0].name screams, "STEVEN!!!" and throws herself into your attack without hesitation.@@
<<set _t to 0>>
<<elseif $enemies[2].dead is false>>
/* If Garnet and Pearl are poofed, Amethyst plays defense */
@@.interruption;
$enemies[2].name yells, "Pick on someone your own size!" and throws herself in front of Steven.@@
<<set _t to 2>>
<<else>>
<<set _t to 3>> /* this is Steven's index; he is vulnerable */
<</if>>
<<set $B.target to $enemies[_t]>>
<<else>>
<<set $bully to false>> /* If you didn't target Steven, lose out on Bully achievement */
<</if>>
<</if>>/* Additional code to perform during battle setup. */
<<restock>>/* Appended to the action phase, immediately after mark attacks and potential surrender failure. Include any custom code or messages you want here. */
<<if !_markActive>>
<<markAttack>>
<</if>>
<<if !_counterActive>>
<<hunterCheck>>
<</if>><<restock>><<if $B.turn == "player" && passage() == "end of round">>
<<for _puppet range puppets()>>
<<if !_puppet.winded && !_puppet.petrified>>
<<set _puppet.en += _puppet.ENregen>>
<</if>>
<</for>>
<<set $B.item_used = false>>
<</if>>/* Any additional logic you want to add to damage calculation. Run after element logic, immediately before damage rounding. */
<<if _target.invincible>>
<<set $dmg = 0; _noDmgFloor = true>>
<<else>>
<<if $B.subject.berserker is true>>
<<set $dmg *= (1+setup.BERSERK_FACTOR)>>
<</if>>
<<if _target.berserker is true>>
<<set $dmg *= (1+setup.BERSERK_FACTOR)>>
<</if>>
<<if $B.subject.defender is true>>
<<set $dmg *= setup.DEFEND_FACTOR>>
<</if>>
<<if _target.defender is true>>
<<set $dmg *= setup.DEFEND_FACTOR>>
<</if>>
<<if _target.shield is true>>
<<set $dmg *= (1-setup.SHIELD_FACTOR)>>
<</if>>
<</if>>/* This passage allows you to define encounters -- that is, unique parties of enemies. To define individual enemies and characters, see the database JS files. */
<<widget "callEncounter">>
/* The order characters are added to the array matters: they are displayed in that order from left to right when the stat blocks instantiate. */
<<switch $args[0]>>
<<case "at1">>
/* Currently Bubblegum's name is too long and makes her box wider than normal. That's fitting though, maybe I should let it stay. */
<<populateEnemies `["Jake","Princess Bubblegum","Finn"]`>>
<<case "gf1">>
<<populateEnemies `["Dipper","Mabel"]`>>
<<case "gum1">>
<<populateEnemies `["Gumball","Anais","Darwin"]`>>
<<case "su1">>
<<populateEnemies `["Pearl","Garnet","Amethyst","Steven"]`>>
<<case "gum2">>
<<populateEnemies `["Nicole"]`>>
<</switch>>
<<if ndef $enemies || $enemies.length == 0>>
/* Dummy enemy so the display doesn't break. */
<<set $enemies to [new Enemy()]>>
<</if>>
<<if def $bestiary>>
<<for _enemy range $enemies>>
<<if def $bestiary.fetch(_enemy.name)>>
<<set $bestiary.fetch(_enemy.name).encountered = true>>
<</if>>
<</for>>
<</if>>
<</widget>>
<<widget "populateEnemies">>
/* Quick way of populating enemy array. Pass array of names in order corresponding to enemy array order, including nulls for empty spaces. */
/* The passed array must be quoted with backticks. */
<<set $enemies = []>>
<<if $args[0] instanceof Array && (setup.BATTLE_GRID !== true || $args[0].length == setup.PARTY_SIZE)>>
<<set _array = $args[0]>>
<<for _n, _name range _array>>
<<if typeof(_name) == 'string'>>
<<set $enemies.push(new Enemy(_name))>>
<<else>>
<<set $enemies.push(_name)>>
<</if>>
<</for>>
<<else>>
<<run console.log("ERROR in populateEnemies: invalid argument")>>
<</if>>
<</widget>><<if $action.accuracy === true>>
<<set _hit = true>>
<<else>>
<<set _acc = $action.accuracy>>
<<if subject().stats.hasOwnProperty("Accuracy")>>
<<set _acc = Math.max(_acc + (subject().get("Accuracy")-100),setup.MIN_ACCURACY)>>
<</if>>
<<if _target.stats.hasOwnProperty("Evasion")>>
<<set _acc = Math.max(_acc - _target.get("Evasion"),setup.MIN_ACCURACY)>>
<</if>>
<<set _toHit = random(1,100)>>
<<if _toHit <= _acc>>
<<set _hit = true>>
<<else>>
<<set _hit = false>>
<</if>>
<</if>><<if Number.isInteger(_target.crisisPoints) && $dmg > 0>>
<<switch _target.crisisMode>>
<<default>>
<<set _target.crisisPoints += Math.round(($dmg/_target.maxhp)*100*_target.crisisFactor)>>
<</switch>>
<</if>><<set _critChance = $action.critRate>>
<<set _toCrit = random(1,100)>>
<<if subject().stats.hasOwnProperty("Skill")>>
<<set _critChance += subject().get("Skill")>>
<</if>>
<<if _toCrit <= _critChance>>
<<print setup.CRIT_MESSAGE+" ">>
<<set $dmg *= $action.critMultiplier>>
<<set _crit = true>>
<</if>>/* Define the damage formula or formulae you want to use here. */
<<if !$action.formula>>
/* Only use the default formula if the action does not have a custom formula (so, the formula property is falsy) */
<<switch setup.formula>>
<<case "subtractive">>
<<set $dmg to ((setup.base+setup.damper*_atk)*_w)-setup.damper*_def>>
<<case "subtractive lumped">>
<<set $dmg to (setup.base+setup.damper*(_atk-_def))*_w>>
<<case "rpgmaker">>
<<set $dmg to (_atk*4-_def*2)*setup.damper*_w>>
<<case "divisive">>
<<set $dmg to (setup.base*(_atk/_def))*_w>>
<<default>>
/* add your own here! */
<</switch>>
<<else>>
<<set $dmg = $action.formula()>>
<</if>>/* Default: Dragon Age style. Attacker gains threat equal to the percentage of maximum HP dealt. Fireflies gain double threat. */
<<set _threatGain = 100 * ($dmg / target().maxhp)>>
<<if subject().firefly>>
<<set _threatGain *= 2>>>
<</if>>
<<run target().threat.inc(subject().name,_threatGain)>><<set _u = $puppets.find(function (p) { return (p.uncontrollable && !(p.isDone || p.noact || p.dead)); })>>
<<if def _u>>
<<set $B.subject = _u>>
<<if subject().name == "Mage">>
<<set $action = new Action("Blast")>>
<<elseif subject().name == "Witch">>
<<set $action = new Action("Pox")>>
<<else>>
<<set $action = clone(subject().defaultAction)>>
<</if>>
<<if subject().en < $action.cost>>
<<set subject().isDone = true>>
<<goto "Battle!">> /* necessary to find other potential uncontrollables */
<<else>>
<<if _u.confusion>>
<<randomTarget "any">>
<<elseif _u.charmed>>
<<randomTarget "allies">>
<<elseif _u.hatred>>
<<randomTarget "enemies">>
<</if>>
<<goto "action phase">>
<</if>>
<</if>>/* Displays in-between the two party listings in actorlist. */<<widget "specialcheck">>
/* Called on the "Battle!" passage. Use this to direct a player to any special scenes or scenarios you want to occur upon specific triggers, such as a character reacting to another character's defeat or changing into a different form. */
<<include "loss of control effects">>
<</widget>><<replace "#actorlist">><</replace>>
Select an item to use from stash. <<backbtn>>
<br/><br/>
<<if $COMPRESSED_ACTIONS === true>>
<<set _actionClass = "compressed">>
<<else>>
<<set _actionClass = "">>
<</if>>
<div id="actionlist" @class="_actionClass">
<<stashlist $B.subject>>
</div>
<<if $COMPRESSED_ACTIONS === true>>
<br/>
<div id="actionInfo">
<<include "action box default">>
</div>
<</if>><<widget "stashlist">>
<<run console.assert($args.length > 0 && ($args[0] instanceof Puppet),"ERROR in stashlist: no Puppet")>>
<<run console.assert($args[0].stash !== undefined,"ERROR in stashlist: stash is undefined")>>
<<set _char = $args[0]>>
<<for _action range _char.stash>>
<div class="actionDisplay">
<<capture _action>>
<<mouseover>>
<span class="actionName">
<b>
<<link _action.name>>
<<actionLink _action>>
<</link>>
</b>
</span>
<<onmouseover>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>>
<<actionInfo _action "full">>
<</replace>>
<</if>>
<<onmouseout>>
<<if $COMPRESSED_ACTIONS === true>>
<<replace '#actionInfo'>><<include "action box default">><</replace>>
<</if>>
<</mouseover>>
<</capture>>
<<if $COMPRESSED_ACTIONS === true && $inbattle>>
<<actionInfo _action "no name">>
<<else>>
<<actionInfo _action "no name" "full">>
<br/>
<</if>>
</div>
<</for>>
<</widget>>/* Appended to the default StoryInit passage. Add any additional code you want here. */
<<cacheaudio "Happy_8bit_Loop_01" setup.SoundPath+"8bitloop.wav">> /* This is to set up the audio for the example music in the engine help file. Feel free to remove it. */
<<set setup.formula to "subtractive">>
/*
Can choose between multiple premade damage formulas here.
subtractive: (base + damper * a.get("Attack")) * weight - damper * b.get("Defense")
subtractive lumped: (base + damper * (a.get("Attack") - b.get("Defense"))) * weight
rpgmaker: (a.get("Attack") * 4 - b.get("Defense") * 2) * damper * weight
divisive: (base * (a.get("Attack")/b.get("Defense"))) * weight
*/
<<set setup.LEVEL_CAP = 10>>
<<set setup.PORTRAIT_SIZE = 50>> /* Portrait size in px, used for save game display */
/* Defining starting inventory. */
<<set $inventory = new Inventory([
new Item("Antidote",1),
new Item("Fire Extinguisher",1),
new Item("Canned Air",1),
new Item("Healing Crystal",1),
new Item("Nanites",1),
new Item("Painkiller",1),
new Item("Asprin",1),
new Item("Panacea",1),
new Item("Bottled Chi",1),
new Item("Stimulant",2),
new Item("Adrenaline",1),
new Item("Stoneskin",1),
new Item("Nootropic",1),
new Item("Throwing Knife",5),
new Item("Powdered Glass",1),
new Item("Grenade",0),
new Item("Flamethrower",0),
new Item("Gas Bomb",0),
new Item("Calamity Bomb",0),
new Item("Flashbang",0),
new Item("Apple of Life",1),
new Item("Symbol of Destruction",1),
new Item("Aura of Protection",1),
new Item("Color of Defeat",1),
new Item("Color of Growth",2),
new Item("Cursed Ring",3)
])>>
<<if setup.formula == "subtractive">>
<<set setup.MIN_STAT = 0>>
<<else>>
<<set setup.MIN_STAT = 1>>
<</if>>
<<if setup.THREAT_TARGETING === true>>
<<set setup.STATUS_SCREENS.battle.push("aggression")>>
<</if>>
<<if setup.BATTLE_GRID === true>>
<<set setup.ROW_SIZE = 3>>
<<set setup.COLUMN_SIZE = 3>>
<<set setup.PARTY_SIZE = setup.ROW_SIZE * setup.COLUMN_SIZE>>
<<set setup.MENU_OPTIONS.push("Formation")>>
<</if>>
/* Initialize bestiary (if you want one) */
<<set $bestiary = new Bestiary()>>
<<run customMods()>>
<<if def $bestiary>>
<<run setup.MENU_OPTIONS.push("Bestiary")>>
<<for _entry range $bestiary>>
<<set _entry.revealAll()>>
<</for>>
<</if>>
<<set $currentArea = "Example Area">>
/* Define the starting party and area here. */
<<set $puppets = [new Puppet("Rogue"),new Puppet("Fighter"),new Puppet("Artist")]>>
<<set $Reserve_Puppets = [new Puppet("Bard"),new Puppet("Archer"),new Puppet("Cleric"),new Puppet("Witch"),new Puppet("Mage")]>>